mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	
							
								
								
									
										13
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -6,6 +6,19 @@ __pycache__/ | ||||
| # C extensions | ||||
| *.so | ||||
|  | ||||
| # Hide sublime text stuff | ||||
| *.sublime-project | ||||
| *.sublime-workspace | ||||
|  | ||||
| # Hide some OS X stuff | ||||
| .DS_Store | ||||
| .AppleDouble | ||||
| .LSOverride | ||||
| Icon | ||||
|  | ||||
| # Thumbnails | ||||
| ._* | ||||
|  | ||||
| # Distribution / packaging | ||||
| .Python | ||||
| build/ | ||||
|   | ||||
| @@ -51,6 +51,6 @@ matrix: | ||||
|         - clang-format-7 -version | ||||
|         - clang-apply-replacements-7 -version | ||||
|       script: | ||||
|         - script/clang-tidy.py --all-headers -j 2 --fix | ||||
|         - script/clang-format.py -i -j 2 | ||||
|         - script/clang-tidy --all-headers -j 2 --fix | ||||
|         - script/clang-format -i -j 2 | ||||
|         - script/ci-suggest-changes | ||||
|   | ||||
| @@ -2,12 +2,12 @@ server { | ||||
|     listen %%port%% default_server ssl http2; | ||||
|  | ||||
|     include /etc/nginx/includes/server_params.conf; | ||||
| 	include /etc/nginx/includes/proxy_params.conf; | ||||
| 	include /etc/nginx/includes/ssl_params.conf; | ||||
| 	# Clear Hass.io Ingress header | ||||
| 	proxy_set_header X-Hassio-Ingress ""; | ||||
|     include /etc/nginx/includes/proxy_params.conf; | ||||
|     include /etc/nginx/includes/ssl_params.conf; | ||||
|     # Clear Hass.io Ingress header | ||||
|     proxy_set_header X-Hassio-Ingress ""; | ||||
|  | ||||
| 	# Redirect http requests to https on the same port. | ||||
|     # Redirect http requests to https on the same port. | ||||
|     # https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/ | ||||
|     error_page 497 https://$http_host$request_uri; | ||||
|  | ||||
|   | ||||
| @@ -7,10 +7,10 @@ server { | ||||
|     proxy_set_header X-Hassio-Ingress "YES"; | ||||
|  | ||||
|     location / { | ||||
|     	# Only allow from Hass.io supervisor | ||||
|         # Only allow from Hass.io supervisor | ||||
|         allow   172.30.32.2; | ||||
|         deny    all; | ||||
|  | ||||
|         proxy_pass http://esphome; | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,16 +1,18 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import argparse | ||||
| import functools | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
| from datetime import datetime | ||||
|  | ||||
| from esphome import const, writer, yaml_util | ||||
| import esphome.codegen as cg | ||||
| from esphome.config import iter_components, read_config, strip_default_ids | ||||
| from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \ | ||||
|     CONF_PASSWORD, CONF_PORT | ||||
| from esphome.core import CORE, EsphomeError | ||||
| from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority | ||||
| from esphome.helpers import color, indent | ||||
| from esphome.py_compat import IS_PY2, safe_input | ||||
| from esphome.util import run_external_command, run_external_process, safe_print | ||||
| @@ -117,12 +119,27 @@ def run_miniterm(config, port): | ||||
|                 config, line, backtrace_state=backtrace_state) | ||||
|  | ||||
|  | ||||
| def wrap_to_code(name, comp): | ||||
|     coro = coroutine(comp.to_code) | ||||
|  | ||||
|     @functools.wraps(comp.to_code) | ||||
|     @coroutine_with_priority(coro.priority) | ||||
|     def wrapped(conf): | ||||
|         cg.add(cg.LineComment(u"{}:".format(name))) | ||||
|         if comp.config_schema is not None: | ||||
|             cg.add(cg.LineComment(indent(yaml_util.dump(conf).decode('utf-8')))) | ||||
|         yield coro(conf) | ||||
|  | ||||
|     return wrapped | ||||
|  | ||||
|  | ||||
| def write_cpp(config): | ||||
|     _LOGGER.info("Generating C++ source...") | ||||
|  | ||||
|     for _, component, conf in iter_components(CORE.config): | ||||
|     for name, component, conf in iter_components(CORE.config): | ||||
|         if component.to_code is not None: | ||||
|             CORE.add_job(component.to_code, conf) | ||||
|             coro = wrap_to_code(name, component) | ||||
|             CORE.add_job(coro, conf) | ||||
|  | ||||
|     CORE.flush_tasks() | ||||
|  | ||||
| @@ -245,7 +262,7 @@ def command_vscode(args): | ||||
|     from esphome import vscode | ||||
|  | ||||
|     CORE.config_path = args.configuration | ||||
|     vscode.read_config() | ||||
|     vscode.read_config(args) | ||||
|  | ||||
|  | ||||
| def command_compile(args, config): | ||||
| @@ -423,7 +440,8 @@ def parse_args(argv): | ||||
|     dashboard.add_argument("--socket", | ||||
|                            help="Make the dashboard serve under a unix socket", type=str) | ||||
|  | ||||
|     subparsers.add_parser('vscode', help=argparse.SUPPRESS) | ||||
|     vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS) | ||||
|     vscode.add_argument('--ace', action='store_true') | ||||
|  | ||||
|     return parser.parse_args(argv[1:]) | ||||
|  | ||||
|   | ||||
| @@ -128,7 +128,7 @@ def or_condition_to_code(config, condition_id, template_arg, args): | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, conditions) | ||||
|  | ||||
|  | ||||
| @register_condition('not', NotCondition, validate_condition) | ||||
| @register_condition('not', NotCondition, validate_potentially_and_condition) | ||||
| def not_condition_to_code(config, condition_id, template_arg, args): | ||||
|     condition = yield build_condition(config, template_arg, args) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, condition) | ||||
|   | ||||
| @@ -10,16 +10,16 @@ | ||||
| # pylint: disable=unused-import | ||||
| from esphome.cpp_generator import (  # noqa | ||||
|     Expression, RawExpression, RawStatement, TemplateArguments, | ||||
|     StructInitializer, ArrayInitializer, safe_exp, Statement, | ||||
|     StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment, | ||||
|     progmem_array, statement, variable, Pvariable, new_Pvariable, | ||||
|     add, add_global, add_library, add_build_flag, add_define, | ||||
|     get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj, | ||||
|     MockObjClass) | ||||
| from esphome.cpp_helpers import (  # noqa | ||||
|     gpio_pin_expression, register_component, build_registry_entry, | ||||
|     build_registry_list, extract_registry_entry_config) | ||||
|     build_registry_list, extract_registry_entry_config, register_parented) | ||||
| from esphome.cpp_types import (  # noqa | ||||
|     global_ns, void, nullptr, float_, bool_, std_ns, std_string, | ||||
|     global_ns, void, nullptr, float_, double, bool_, std_ns, std_string, | ||||
|     std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN, | ||||
|     esphome_ns, App, Nameable, Component, ComponentPtr, | ||||
|     PollingComponent, Application, optional, arduino_json_ns, JsonObject, | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| #include "esphome/components/adc/adc_sensor.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef USE_ADC_SENSOR_VCC | ||||
| ADC_MODE(ADC_VCC) | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace adc { | ||||
|  | ||||
|   | ||||
| @@ -2,16 +2,13 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/esphal.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/voltage_sampler/voltage_sampler.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace adc { | ||||
|  | ||||
| #ifdef USE_ADC_SENSOR_VCC | ||||
| ADC_MODE(ADC_VCC) | ||||
| #endif | ||||
|  | ||||
| class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { | ||||
|  public: | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|   | ||||
| @@ -87,7 +87,7 @@ HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({ | ||||
|         cv.string: cv.string, | ||||
|     }), | ||||
|     cv.Optional(CONF_VARIABLES): cv.Schema({ | ||||
|         cv.string: cv.lambda_, | ||||
|         cv.string: cv.returning_lambda, | ||||
|     }), | ||||
| }) | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \ | ||||
|     CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR | ||||
|  | ||||
| bang_bang_ns = cg.esphome_ns.namespace('bang_bang') | ||||
| BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.ClimateDevice) | ||||
| BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate) | ||||
| BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig') | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ | ||||
|   | ||||
| @@ -70,7 +70,7 @@ def delayed_off_filter_to_code(config, filter_id): | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register('lambda', LambdaFilter, cv.lambda_) | ||||
| @FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_lambda) | ||||
| def lambda_filter_to_code(config, filter_id): | ||||
|     lambda_ = yield cg.process_lambda(config, [(bool, 'x')], return_type=cg.optional.template(bool)) | ||||
|     yield cg.new_Pvariable(filter_id, lambda_) | ||||
|   | ||||
| @@ -5,7 +5,7 @@ namespace esphome { | ||||
|  | ||||
| namespace binary_sensor { | ||||
|  | ||||
| static const char *TAG = "something.Filter"; | ||||
| static const char *TAG = "sensor.filter"; | ||||
|  | ||||
| void Filter::output(bool value, bool is_initial) { | ||||
|   if (!this->dedup_.next(value)) | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| namespace esphome { | ||||
| namespace ble_presence { | ||||
|  | ||||
| static const char *TAG = "something.something"; | ||||
| static const char *TAG = "ble_presence"; | ||||
|  | ||||
| void BLEPresenceDevice::dump_config() { LOG_BINARY_SENSOR("", "BLE Presence", this); } | ||||
|  | ||||
|   | ||||
| @@ -232,7 +232,7 @@ float BME680Component::get_setup_priority() const { return setup_priority::DATA; | ||||
| void BME680Component::update() { | ||||
|   uint8_t meas_control = 0;  // No need to fetch, we're setting all fields | ||||
|   meas_control |= (this->temperature_oversampling_ & 0b111) << 5; | ||||
|   meas_control |= (this->pressure_oversampling_ & 0b111) << 5; | ||||
|   meas_control |= (this->pressure_oversampling_ & 0b111) << 2; | ||||
|   meas_control |= 0b01;  // forced mode | ||||
|   if (!this->write_byte(BME680_REGISTER_CONTROL_MEAS, meas_control)) { | ||||
|     this->status_set_warning(); | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/ccs811/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/ccs811/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										147
									
								
								esphome/components/ccs811/ccs811.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								esphome/components/ccs811/ccs811.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | ||||
| #include "ccs811.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ccs811 { | ||||
|  | ||||
| static const char *TAG = "ccs811"; | ||||
|  | ||||
| // based on | ||||
| //  - https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf | ||||
|  | ||||
| #define CHECK_TRUE(f, error_code) \ | ||||
|   if (!(f)) { \ | ||||
|     this->mark_failed(); \ | ||||
|     this->error_code_ = (error_code); \ | ||||
|     return; \ | ||||
|   } | ||||
|  | ||||
| #define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICAITON_FAILED) | ||||
|  | ||||
| void CCS811Component::setup() { | ||||
|   // page 9 programming guide - hwid is always 0x81 | ||||
|   uint8_t hw_id; | ||||
|   CHECKED_IO(this->read_byte(0x20, &hw_id)) | ||||
|   CHECK_TRUE(hw_id == 0x81, INVALID_ID) | ||||
|  | ||||
|   // software reset, page 3 - allowed to fail | ||||
|   this->write_bytes(0xFF, {0x11, 0xE5, 0x72, 0x8A}); | ||||
|   delay(5); | ||||
|  | ||||
|   // page 10, APP_START | ||||
|   CHECK_TRUE(!this->status_has_error_(), SENSOR_REPORTED_ERROR) | ||||
|   CHECK_TRUE(this->status_app_is_valid_(), APP_INVALID) | ||||
|   CHECK_TRUE(this->write_bytes(0xF4, {}), APP_START_FAILED) | ||||
|   // App setup, wait for it to load | ||||
|   delay(1); | ||||
|  | ||||
|   // set MEAS_MODE (page 5) | ||||
|   uint8_t meas_mode = 0; | ||||
|   uint32_t interval = this->get_update_interval(); | ||||
|   if (interval <= 1000) | ||||
|     meas_mode = 1 << 4; | ||||
|   else if (interval <= 10000) | ||||
|     meas_mode = 2 << 4; | ||||
|   else | ||||
|     meas_mode = 3 << 4; | ||||
|  | ||||
|   CHECKED_IO(this->write_byte(0x01, meas_mode)) | ||||
|  | ||||
|   if (this->baseline_.has_value()) { | ||||
|     // baseline available, write to sensor | ||||
|     this->write_bytes(0x11, decode_uint16(*this->baseline_)); | ||||
|   } | ||||
| } | ||||
| void CCS811Component::update() { | ||||
|   if (!this->status_has_data_()) | ||||
|     this->status_set_warning(); | ||||
|  | ||||
|   // page 12 - alg result data | ||||
|   auto alg_data = this->read_bytes<4>(0x02); | ||||
|   if (!alg_data.has_value()) { | ||||
|     ESP_LOGW(TAG, "Reading CCS811 data failed!"); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|   auto res = *alg_data; | ||||
|   uint16_t co2 = encode_uint16(res[0], res[1]); | ||||
|   uint16_t tvoc = encode_uint16(res[2], res[3]); | ||||
|  | ||||
|   // also print baseline | ||||
|   auto baseline_data = this->read_bytes<2>(0x11); | ||||
|   uint16_t baseline = 0; | ||||
|   if (baseline_data.has_value()) { | ||||
|     baseline = encode_uint16((*baseline_data)[0], (*baseline_data)[1]); | ||||
|   } | ||||
|  | ||||
|   ESP_LOGD(TAG, "Got co2=%u ppm, tvoc=%u ppb, baseline=0x%04X", co2, tvoc, baseline); | ||||
|  | ||||
|   if (this->co2_ != nullptr) | ||||
|     this->co2_->publish_state(co2); | ||||
|   if (this->tvoc_ != nullptr) | ||||
|     this->tvoc_->publish_state(tvoc); | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
|  | ||||
|   this->send_env_data_(); | ||||
| } | ||||
| void CCS811Component::send_env_data_() { | ||||
|   if (this->humidity_ == nullptr && this->temperature_ == nullptr) | ||||
|     return; | ||||
|  | ||||
|   float humidity = NAN; | ||||
|   if (this->humidity_ != nullptr) | ||||
|     humidity = this->humidity_->state; | ||||
|   if (isnan(humidity) || humidity < 0 || humidity > 100) | ||||
|     humidity = 50; | ||||
|   float temperature = NAN; | ||||
|   if (this->temperature_ != nullptr) | ||||
|     temperature = this->temperature_->state; | ||||
|   if (isnan(temperature) || temperature < -25 || temperature > 50) | ||||
|     temperature = 25; | ||||
|   // temperature has a 25° offset to allow negative temperatures | ||||
|   temperature += 25; | ||||
|  | ||||
|   // only 0.5 fractions are supported (application note) | ||||
|   auto hum_value = static_cast<uint8_t>(roundf(humidity * 2)); | ||||
|   auto temp_value = static_cast<uint8_t>(roundf(temperature * 2)); | ||||
|   this->write_bytes(0x05, {hum_value, 0x00, temp_value, 0x00}); | ||||
| } | ||||
| void CCS811Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "CCS811"); | ||||
|   LOG_I2C_DEVICE(this) | ||||
|   LOG_UPDATE_INTERVAL(this) | ||||
|   LOG_SENSOR("  ", "CO2 Sensor", this->co2_) | ||||
|   LOG_SENSOR("  ", "TVOC Sensor", this->tvoc_) | ||||
|   if (this->baseline_) { | ||||
|     ESP_LOGCONFIG(TAG, "  Baseline: %04X", *this->baseline_); | ||||
|   } else { | ||||
|     ESP_LOGCONFIG(TAG, "  Baseline: NOT SET"); | ||||
|   } | ||||
|   if (this->is_failed()) { | ||||
|     switch (this->error_code_) { | ||||
|       case COMMUNICAITON_FAILED: | ||||
|         ESP_LOGW(TAG, "Communication failed! Is the sensor connected?"); | ||||
|         break; | ||||
|       case INVALID_ID: | ||||
|         ESP_LOGW(TAG, "Sensor reported an invalid ID. Is this a CCS811?"); | ||||
|         break; | ||||
|       case SENSOR_REPORTED_ERROR: | ||||
|         ESP_LOGW(TAG, "Sensor reported internal error"); | ||||
|         break; | ||||
|       case APP_INVALID: | ||||
|         ESP_LOGW(TAG, "Sensor reported invalid APP installed."); | ||||
|         break; | ||||
|       case APP_START_FAILED: | ||||
|         ESP_LOGW(TAG, "Sensor reported APP start failed."); | ||||
|         break; | ||||
|       case UNKNOWN: | ||||
|       default: | ||||
|         ESP_LOGW(TAG, "Unknown setup error!"); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace ccs811 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										54
									
								
								esphome/components/ccs811/ccs811.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								esphome/components/ccs811/ccs811.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/preferences.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ccs811 { | ||||
|  | ||||
| class CCS811Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void set_co2(sensor::Sensor *co2) { co2_ = co2; } | ||||
|   void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; } | ||||
|   void set_baseline(uint16_t baseline) { baseline_ = baseline; } | ||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||
|  | ||||
|   /// Setup the sensor and test for a connection. | ||||
|   void setup() override; | ||||
|   /// Schedule temperature+pressure readings. | ||||
|   void update() override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   optional<uint8_t> read_status_() { return this->read_byte(0x00); } | ||||
|   bool status_has_error_() { return this->read_status_().value_or(1) & 1; } | ||||
|   bool status_app_is_valid_() { return this->read_status_().value_or(0) & (1 << 4); } | ||||
|   bool status_has_data_() { return this->read_status_().value_or(0) & (1 << 3); } | ||||
|   void send_env_data_(); | ||||
|  | ||||
|   enum ErrorCode { | ||||
|     UNKNOWN, | ||||
|     COMMUNICAITON_FAILED, | ||||
|     INVALID_ID, | ||||
|     SENSOR_REPORTED_ERROR, | ||||
|     APP_INVALID, | ||||
|     APP_START_FAILED, | ||||
|   } error_code_{UNKNOWN}; | ||||
|  | ||||
|   sensor::Sensor *co2_{nullptr}; | ||||
|   sensor::Sensor *tvoc_{nullptr}; | ||||
|   optional<uint16_t> baseline_{}; | ||||
|   /// Input sensor for humidity reading. | ||||
|   sensor::Sensor *humidity_{nullptr}; | ||||
|   /// Input sensor for temperature reading. | ||||
|   sensor::Sensor *temperature_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace ccs811 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										46
									
								
								esphome/components/ccs811/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								esphome/components/ccs811/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ | ||||
|     UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_PERIODIC_TABLE_CO2 | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| ccs811_ns = cg.esphome_ns.namespace('ccs811') | ||||
| CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice) | ||||
|  | ||||
| CONF_ECO2 = 'eco2' | ||||
| CONF_TVOC = 'tvoc' | ||||
| CONF_BASELINE = 'baseline' | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CCS811Component), | ||||
|     cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_PERIODIC_TABLE_CO2, | ||||
|                                                  0), | ||||
|     cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0), | ||||
|  | ||||
|     cv.Optional(CONF_BASELINE): cv.hex_uint16_t, | ||||
|     cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor), | ||||
|     cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5A)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     sens = yield sensor.new_sensor(config[CONF_ECO2]) | ||||
|     cg.add(var.set_co2(sens)) | ||||
|     sens = yield sensor.new_sensor(config[CONF_TVOC]) | ||||
|     cg.add(var.set_tvoc(sens)) | ||||
|  | ||||
|     if CONF_BASELINE in config: | ||||
|         cg.add(var.set_baseline(config[CONF_BASELINE])) | ||||
|  | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         sens = yield cg.get_variable(config[CONF_TEMPERATURE]) | ||||
|         cg.add(var.set_temperature(sens)) | ||||
|     if CONF_HUMIDITY in config: | ||||
|         sens = yield cg.get_variable(config[CONF_HUMIDITY]) | ||||
|         cg.add(var.set_humidity(sens)) | ||||
| @@ -12,7 +12,7 @@ IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| climate_ns = cg.esphome_ns.namespace('climate') | ||||
|  | ||||
| ClimateDevice = climate_ns.class_('Climate', cg.Nameable) | ||||
| Climate = climate_ns.class_('Climate', cg.Nameable) | ||||
| ClimateCall = climate_ns.class_('ClimateCall') | ||||
| ClimateTraits = climate_ns.class_('ClimateTraits') | ||||
|  | ||||
| @@ -30,7 +30,7 @@ validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) | ||||
| ControlAction = climate_ns.class_('ControlAction', automation.Action) | ||||
|  | ||||
| CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(ClimateDevice), | ||||
|     cv.GenerateID(): cv.declare_id(Climate), | ||||
|     cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTClimateComponent), | ||||
|     cv.Optional(CONF_VISUAL, default={}): cv.Schema({ | ||||
|         cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, | ||||
| @@ -68,7 +68,7 @@ def register_climate(var, config): | ||||
|  | ||||
|  | ||||
| CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({ | ||||
|     cv.Required(CONF_ID): cv.use_id(ClimateDevice), | ||||
|     cv.Required(CONF_ID): cv.use_id(Climate), | ||||
|     cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode), | ||||
|     cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), | ||||
|     cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), | ||||
|   | ||||
| @@ -207,11 +207,6 @@ ClimateTraits Climate::get_traits() { | ||||
|   return traits; | ||||
| } | ||||
|  | ||||
| #ifdef USE_MQTT_CLIMATE | ||||
| MQTTClimateComponent *Climate::get_mqtt() const { return this->mqtt_; } | ||||
| void Climate::set_mqtt(MQTTClimateComponent *mqtt) { this->mqtt_ = mqtt; } | ||||
| #endif | ||||
|  | ||||
| void Climate::set_visual_min_temperature_override(float visual_min_temperature_override) { | ||||
|   this->visual_min_temperature_override_ = visual_min_temperature_override; | ||||
| } | ||||
|   | ||||
| @@ -169,11 +169,6 @@ class Climate : public Nameable { | ||||
|    */ | ||||
|   ClimateTraits get_traits(); | ||||
|  | ||||
| #ifdef USE_MQTT_COVER | ||||
|   MQTTClimateComponent *get_mqtt() const; | ||||
|   void set_mqtt(MQTTClimateComponent *mqtt); | ||||
| #endif | ||||
|  | ||||
|   void set_visual_min_temperature_override(float visual_min_temperature_override); | ||||
|   void set_visual_max_temperature_override(float visual_max_temperature_override); | ||||
|   void set_visual_temperature_step_override(float visual_temperature_step_override); | ||||
|   | ||||
| @@ -6,13 +6,13 @@ namespace climate { | ||||
| const char *climate_mode_to_string(ClimateMode mode) { | ||||
|   switch (mode) { | ||||
|     case CLIMATE_MODE_OFF: | ||||
|       return "OFF"; | ||||
|       return "off"; | ||||
|     case CLIMATE_MODE_AUTO: | ||||
|       return "AUTO"; | ||||
|       return "auto"; | ||||
|     case CLIMATE_MODE_COOL: | ||||
|       return "COOL"; | ||||
|       return "cool"; | ||||
|     case CLIMATE_MODE_HEAT: | ||||
|       return "HEAT"; | ||||
|       return "heat"; | ||||
|     default: | ||||
|       return "UNKNOWN"; | ||||
|   } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ CustomBinarySensorConstructor = custom_ns.class_('CustomBinarySensorConstructor' | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor), | ||||
|     cv.Required(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|     cv.Required(CONF_BINARY_SENSORS): cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA), | ||||
| }) | ||||
|  | ||||
|   | ||||
							
								
								
									
										26
									
								
								esphome/components/custom/climate/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/custom/climate/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import climate | ||||
| from esphome.const import CONF_ID, CONF_LAMBDA | ||||
| from .. import custom_ns | ||||
|  | ||||
| CustomClimateConstructor = custom_ns.class_('CustomClimateConstructor') | ||||
| CONF_CLIMATES = 'climates' | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CustomClimateConstructor), | ||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|     cv.Required(CONF_CLIMATES): cv.ensure_list(climate.CLIMATE_SCHEMA), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     template_ = yield cg.process_lambda( | ||||
|         config[CONF_LAMBDA], [], | ||||
|         return_type=cg.std_vector.template(climate.Climate.operator('ptr'))) | ||||
|  | ||||
|     rhs = CustomClimateConstructor(template_) | ||||
|     custom = cg.variable(config[CONF_ID], rhs) | ||||
|     for i, conf in enumerate(config[CONF_CLIMATES]): | ||||
|         rhs = custom.Pget_climate(i) | ||||
|         yield climate.register_climate(rhs, conf) | ||||
							
								
								
									
										20
									
								
								esphome/components/custom/climate/custom_climate.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/custom/climate/custom_climate.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/climate/climate.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace custom { | ||||
|  | ||||
| class CustomClimateConstructor { | ||||
|  public: | ||||
|   CustomClimateConstructor(const std::function<std::vector<climate::Climate *>()> &init) { this->climates_ = init(); } | ||||
|  | ||||
|   climate::Climate *get_climate(int i) { return this->climates_[i]; } | ||||
|  | ||||
|  protected: | ||||
|   std::vector<climate::Climate *> climates_; | ||||
| }; | ||||
|  | ||||
| }  // namespace custom | ||||
| }  // namespace esphome | ||||
							
								
								
									
										26
									
								
								esphome/components/custom/cover/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/custom/cover/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import cover | ||||
| from esphome.const import CONF_ID, CONF_LAMBDA | ||||
| from .. import custom_ns | ||||
|  | ||||
| CustomCoverConstructor = custom_ns.class_('CustomCoverConstructor') | ||||
| CONF_COVERS = 'covers' | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CustomCoverConstructor), | ||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|     cv.Required(CONF_COVERS): cv.ensure_list(cover.COVER_SCHEMA), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     template_ = yield cg.process_lambda( | ||||
|         config[CONF_LAMBDA], [], | ||||
|         return_type=cg.std_vector.template(cover.Cover.operator('ptr'))) | ||||
|  | ||||
|     rhs = CustomCoverConstructor(template_) | ||||
|     custom = cg.variable(config[CONF_ID], rhs) | ||||
|     for i, conf in enumerate(config[CONF_COVERS]): | ||||
|         rhs = custom.Pget_cover(i) | ||||
|         yield cover.register_cover(rhs, conf) | ||||
							
								
								
									
										19
									
								
								esphome/components/custom/cover/custom_cover.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/components/custom/cover/custom_cover.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/cover/cover.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace custom { | ||||
|  | ||||
| class CustomCoverConstructor { | ||||
|  public: | ||||
|   CustomCoverConstructor(const std::function<std::vector<cover::Cover *>()> &init) { this->covers_ = init(); } | ||||
|  | ||||
|   cover::Cover *get_cover(int i) { return this->covers_[i]; } | ||||
|  | ||||
|  protected: | ||||
|   std::vector<cover::Cover *> covers_; | ||||
| }; | ||||
|  | ||||
| }  // namespace custom | ||||
| }  // namespace esphome | ||||
							
								
								
									
										26
									
								
								esphome/components/custom/light/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/custom/light/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import light | ||||
| from esphome.const import CONF_ID, CONF_LAMBDA | ||||
| from .. import custom_ns | ||||
|  | ||||
| CustomLightOutputConstructor = custom_ns.class_('CustomLightOutputConstructor') | ||||
| CONF_LIGHTS = 'lights' | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CustomLightOutputConstructor), | ||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|     cv.Required(CONF_LIGHTS): cv.ensure_list(light.RGB_LIGHT_SCHEMA), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     template_ = yield cg.process_lambda( | ||||
|         config[CONF_LAMBDA], [], | ||||
|         return_type=cg.std_vector.template(light.LightOutput.operator('ptr'))) | ||||
|  | ||||
|     rhs = CustomLightOutputConstructor(template_) | ||||
|     custom = cg.variable(config[CONF_ID], rhs) | ||||
|     for i, conf in enumerate(config[CONF_LIGHTS]): | ||||
|         rhs = custom.Pget_light(i) | ||||
|         yield light.register_light(rhs, conf) | ||||
							
								
								
									
										22
									
								
								esphome/components/custom/light/custom_light_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								esphome/components/custom/light/custom_light_output.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/light/light_output.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace custom { | ||||
|  | ||||
| class CustomLightOutputConstructor { | ||||
|  public: | ||||
|   CustomLightOutputConstructor(const std::function<std::vector<light::LightOutput *>()> &init) { | ||||
|     this->outputs_ = init(); | ||||
|   } | ||||
|  | ||||
|   light::LightOutput *get_light(int i) { return this->outputs_[i]; } | ||||
|  | ||||
|  protected: | ||||
|   std::vector<light::LightOutput *> outputs_; | ||||
| }; | ||||
|  | ||||
| }  // namespace custom | ||||
| }  // namespace esphome | ||||
| @@ -7,42 +7,27 @@ from .. import custom_ns | ||||
| CustomBinaryOutputConstructor = custom_ns.class_('CustomBinaryOutputConstructor') | ||||
| CustomFloatOutputConstructor = custom_ns.class_('CustomFloatOutputConstructor') | ||||
|  | ||||
| BINARY_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor), | ||||
|     cv.Required(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Required(CONF_TYPE): 'binary', | ||||
|     cv.Required(CONF_OUTPUTS): | ||||
|         cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({ | ||||
|             cv.GenerateID(): cv.declare_id(output.BinaryOutput), | ||||
|         })), | ||||
| }) | ||||
| CONF_BINARY = 'binary' | ||||
| CONF_FLOAT = 'float' | ||||
|  | ||||
| FLOAT_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor), | ||||
|     cv.Required(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Required(CONF_TYPE): 'float', | ||||
|     cv.Required(CONF_OUTPUTS): | ||||
|         cv.ensure_list(output.FLOAT_OUTPUT_SCHEMA.extend({ | ||||
|             cv.GenerateID(): cv.declare_id(output.FloatOutput), | ||||
|         })), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def validate_custom_output(value): | ||||
|     if not isinstance(value, dict): | ||||
|         raise cv.Invalid("Value must be dict") | ||||
|     if CONF_TYPE not in value: | ||||
|         raise cv.Invalid("type not specified!") | ||||
|     type = cv.string_strict(value[CONF_TYPE]).lower() | ||||
|     value[CONF_TYPE] = type | ||||
|     if type == 'binary': | ||||
|         return BINARY_SCHEMA(value) | ||||
|     if type == 'float': | ||||
|         return FLOAT_SCHEMA(value) | ||||
|     raise cv.Invalid("type must either be binary or float, not {}!".format(type)) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = validate_custom_output | ||||
| CONFIG_SCHEMA = cv.typed_schema({ | ||||
|     CONF_BINARY: cv.Schema({ | ||||
|         cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor), | ||||
|         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|         cv.Required(CONF_OUTPUTS): | ||||
|             cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({ | ||||
|                 cv.GenerateID(): cv.declare_id(output.BinaryOutput), | ||||
|             })), | ||||
|     }), | ||||
|     CONF_FLOAT: cv.Schema({ | ||||
|         cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor), | ||||
|         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|         cv.Required(CONF_OUTPUTS): | ||||
|             cv.ensure_list(output.FLOAT_OUTPUT_SCHEMA.extend({ | ||||
|                 cv.GenerateID(): cv.declare_id(output.FloatOutput), | ||||
|             })), | ||||
|     }) | ||||
| }, lower=True) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -8,7 +8,7 @@ CustomSensorConstructor = custom_ns.class_('CustomSensorConstructor') | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CustomSensorConstructor), | ||||
|     cv.Required(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|     cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA), | ||||
| }) | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ CustomSwitchConstructor = custom_ns.class_('CustomSwitchConstructor') | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CustomSwitchConstructor), | ||||
|     cv.Required(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|     cv.Required(CONF_SWITCHES): | ||||
|         cv.ensure_list(switch.SWITCH_SCHEMA.extend({ | ||||
|             cv.GenerateID(): cv.declare_id(switch.Switch), | ||||
|   | ||||
| @@ -8,7 +8,7 @@ CustomTextSensorConstructor = custom_ns.class_('CustomTextSensorConstructor') | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor), | ||||
|     cv.Required(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|     cv.Required(CONF_TEXT_SENSORS): | ||||
|         cv.ensure_list(text_sensor.TEXT_SENSOR_SCHEMA.extend({ | ||||
|             cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), | ||||
|   | ||||
| @@ -8,7 +8,7 @@ CustomComponentConstructor = custom_component_ns.class_('CustomComponentConstruc | ||||
| MULTI_CONF = True | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CustomComponentConstructor), | ||||
|     cv.Required(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|     cv.Optional(CONF_COMPONENTS): cv.ensure_list(cv.Schema({ | ||||
|         cv.GenerateID(): cv.declare_id(cg.Component) | ||||
|     }).extend(cv.COMPONENT_SCHEMA)), | ||||
|   | ||||
| @@ -13,7 +13,7 @@ CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).e | ||||
|  | ||||
|     cv.Optional(CONF_ADDRESS): cv.hex_int, | ||||
|     cv.Optional(CONF_INDEX): cv.positive_int, | ||||
|     cv.Optional(CONF_RESOLUTION, default=12): cv.All(cv.int_, cv.Range(min=9, max=12)), | ||||
|     cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12), | ||||
| }), cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX)) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -55,7 +55,7 @@ CONF_BRIGHTNESS = 'brightness' | ||||
| CONF_SATURATION = 'saturation' | ||||
| CONF_TEST_PATTERN = 'test_pattern' | ||||
|  | ||||
| camera_range_param = cv.All(cv.int_, cv.Range(min=-2, max=2)) | ||||
| camera_range_param = cv.int_range(min=-2, max=2) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(ESP32Camera), | ||||
| @@ -81,7 +81,7 @@ CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.Optional(CONF_IDLE_FRAMERATE, default='0.1 fps'): cv.All(cv.framerate, | ||||
|                                                                 cv.Range(min=0, max=1)), | ||||
|     cv.Optional(CONF_RESOLUTION, default='640X480'): cv.enum(FRAME_SIZES, upper=True), | ||||
|     cv.Optional(CONF_JPEG_QUALITY, default=10): cv.All(cv.int_, cv.Range(min=10, max=63)), | ||||
|     cv.Optional(CONF_JPEG_QUALITY, default=10): cv.int_range(min=10, max=63), | ||||
|     cv.Optional(CONF_CONTRAST, default=0): camera_range_param, | ||||
|     cv.Optional(CONF_BRIGHTNESS, default=0): camera_range_param, | ||||
|     cv.Optional(CONF_SATURATION, default=0): camera_range_param, | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| namespace esphome { | ||||
| namespace esp8266_pwm { | ||||
|  | ||||
| static const char *TAG = "something.something"; | ||||
| static const char *TAG = "esp8266_pwm"; | ||||
|  | ||||
| void ESP8266PWM::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up ESP8266 PWM Output..."); | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import light, power_supply | ||||
| from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MAX_REFRESH_RATE, \ | ||||
|     CONF_POWER_SUPPLY | ||||
| from esphome.components import light | ||||
| from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MAX_REFRESH_RATE | ||||
| from esphome.core import coroutine | ||||
|  | ||||
| fastled_base_ns = cg.esphome_ns.namespace('fastled_base') | ||||
| @@ -24,8 +23,6 @@ BASE_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend({ | ||||
|     cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, | ||||
|     cv.Optional(CONF_RGB_ORDER): cv.one_of(*RGB_ORDERS, upper=True), | ||||
|     cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, | ||||
|  | ||||
|     cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| @@ -37,10 +34,6 @@ def new_fastled_light(config): | ||||
|     if CONF_MAX_REFRESH_RATE in config: | ||||
|         cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE])) | ||||
|  | ||||
|     if CONF_POWER_SUPPLY in config: | ||||
|         var_ = yield cg.get_variable(config[CONF_POWER_SUPPLY]) | ||||
|         cg.add(var.set_power_supply(var_)) | ||||
|  | ||||
|     yield light.register_light(var, config) | ||||
|     cg.add_library('FastLED', '3.2.0') | ||||
|     yield var | ||||
|   | ||||
| @@ -33,27 +33,6 @@ void FastLEDLightOutput::loop() { | ||||
|   this->mark_shown_(); | ||||
|  | ||||
|   ESP_LOGVV(TAG, "Writing RGB values to bus..."); | ||||
|  | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|   if (this->power_supply_ != nullptr) { | ||||
|     bool is_on = false; | ||||
|     for (int i = 0; i < this->num_leds_; i++) { | ||||
|       if (bool(this->leds_[i])) { | ||||
|         is_on = true; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (is_on && !this->has_requested_high_power_) { | ||||
|       this->power_supply_->request_high_power(); | ||||
|       this->has_requested_high_power_ = true; | ||||
|     } | ||||
|     if (!is_on && this->has_requested_high_power_) { | ||||
|       this->power_supply_->unrequest_high_power(); | ||||
|       this->has_requested_high_power_ = false; | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
|   this->controller_->showLeds(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,10 +4,6 @@ | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/components/light/addressable_light.h" | ||||
|  | ||||
| #ifdef USE_POWER_SUPPLY | ||||
| #include "esphome/components/power_supply/power_supply.h" | ||||
| #endif | ||||
|  | ||||
| #define FASTLED_ESP8266_RAW_PIN_ORDER | ||||
| #define FASTLED_ESP32_RAW_PIN_ORDER | ||||
| #define FASTLED_RMT_BUILTIN_DRIVER true | ||||
| @@ -30,10 +26,6 @@ class FastLEDLightOutput : public Component, public light::AddressableLight { | ||||
|   /// Set a maximum refresh rate in µs as some lights do not like being updated too often. | ||||
|   void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } | ||||
|  | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|   void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_supply_ = power_supply; } | ||||
| #endif | ||||
|  | ||||
|   /// Add some LEDS, can only be called once. | ||||
|   CLEDController &add_leds(CLEDController *controller, int num_leds) { | ||||
|     this->controller_ = controller; | ||||
| @@ -242,10 +234,6 @@ class FastLEDLightOutput : public Component, public light::AddressableLight { | ||||
|   int num_leds_{0}; | ||||
|   uint32_t last_refresh_{0}; | ||||
|   optional<uint32_t> max_refresh_rate_{}; | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|   power_supply::PowerSupply *power_supply_{nullptr}; | ||||
|   bool has_requested_high_power_{false}; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| }  // namespace fastled_base | ||||
|   | ||||
| @@ -70,7 +70,7 @@ FONT_SCHEMA = cv.Schema({ | ||||
|     cv.Required(CONF_ID): cv.declare_id(Font), | ||||
|     cv.Required(CONF_FILE): validate_truetype_file, | ||||
|     cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs, | ||||
|     cv.Optional(CONF_SIZE, default=20): cv.All(cv.int_, cv.Range(min=1)), | ||||
|     cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1), | ||||
|     cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||
| }) | ||||
|  | ||||
|   | ||||
							
								
								
									
										23
									
								
								esphome/components/gps/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/gps/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import uart | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| DEPENDENCIES = ['uart'] | ||||
|  | ||||
| gps_ns = cg.esphome_ns.namespace('gps') | ||||
| GPS = gps_ns.class_('GPS', cg.Component, uart.UARTDevice) | ||||
| GPSListener = gps_ns.class_('GPSListener') | ||||
|  | ||||
| CONF_GPS_ID = 'gps_id' | ||||
| MULTI_CONF = True | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(GPS), | ||||
| }).extend(cv.COMPONENT_SCHEMA).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) | ||||
|     cg.add_library('TinyGPSPlus', '1.0.2') | ||||
							
								
								
									
										12
									
								
								esphome/components/gps/gps.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/gps/gps.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #include "gps.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace gps { | ||||
|  | ||||
| static const char *TAG = "gps"; | ||||
|  | ||||
| TinyGPSPlus &GPSListener::get_tiny_gps() { return this->parent_->get_tiny_gps(); } | ||||
|  | ||||
| }  // namespace gps | ||||
| }  // namespace esphome | ||||
							
								
								
									
										47
									
								
								esphome/components/gps/gps.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								esphome/components/gps/gps.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #include <TinyGPS++.h> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace gps { | ||||
|  | ||||
| class GPS; | ||||
|  | ||||
| class GPSListener { | ||||
|  public: | ||||
|   virtual void on_update(TinyGPSPlus &tiny_gps) = 0; | ||||
|   TinyGPSPlus &get_tiny_gps(); | ||||
|  | ||||
|  protected: | ||||
|   friend GPS; | ||||
|  | ||||
|   GPS *parent_; | ||||
| }; | ||||
|  | ||||
| class GPS : public Component, public uart::UARTDevice { | ||||
|  public: | ||||
|   void register_listener(GPSListener *listener) { | ||||
|     listener->parent_ = this; | ||||
|     this->listeners_.push_back(listener); | ||||
|   } | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|   void loop() override { | ||||
|     while (this->available() && !this->has_time_) { | ||||
|       if (this->tiny_gps_.encode(this->read())) { | ||||
|         for (auto *listener : this->listeners_) | ||||
|           listener->on_update(this->tiny_gps_); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   TinyGPSPlus &get_tiny_gps() { return this->tiny_gps_; } | ||||
|  | ||||
|  protected: | ||||
|   bool has_time_{false}; | ||||
|   TinyGPSPlus tiny_gps_; | ||||
|   std::vector<GPSListener *> listeners_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace gps | ||||
| }  // namespace esphome | ||||
							
								
								
									
										23
									
								
								esphome/components/gps/time/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/gps/time/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| from esphome.components import time as time_ | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
| from esphome.const import CONF_ID | ||||
| from .. import gps_ns, GPSListener, CONF_GPS_ID, GPS | ||||
|  | ||||
| DEPENDENCIES = ['gps'] | ||||
|  | ||||
| GPSTime = gps_ns.class_('GPSTime', time_.RealTimeClock, GPSListener) | ||||
|  | ||||
| CONFIG_SCHEMA = time_.TIME_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(GPSTime), | ||||
|     cv.GenerateID(CONF_GPS_ID): cv.use_id(GPS), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield time_.register_time(var, config) | ||||
|     yield cg.register_component(var, config) | ||||
|  | ||||
|     paren = yield cg.get_variable(config[CONF_GPS_ID]) | ||||
|     cg.add(paren.register_listener(var)) | ||||
							
								
								
									
										10
									
								
								esphome/components/gps/time/gps_time.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								esphome/components/gps/time/gps_time.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| #include "gps_time.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace gps { | ||||
|  | ||||
| static const char *TAG = "gps.time"; | ||||
|  | ||||
| }  // namespace gps | ||||
| }  // namespace esphome | ||||
							
								
								
									
										39
									
								
								esphome/components/gps/time/gps_time.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								esphome/components/gps/time/gps_time.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/time/real_time_clock.h" | ||||
| #include "esphome/components/gps/gps.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace gps { | ||||
|  | ||||
| class GPSTime : public time::RealTimeClock, public GPSListener { | ||||
|  public: | ||||
|   void on_update(TinyGPSPlus &tiny_gps) override { | ||||
|     if (!this->has_time_) | ||||
|       this->from_tiny_gps_(tiny_gps); | ||||
|   } | ||||
|   void setup() override { | ||||
|     this->set_interval(5 * 60 * 1000, [this]() { this->from_tiny_gps_(this->get_tiny_gps()); }); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   void from_tiny_gps_(TinyGPSPlus &tiny_gps) { | ||||
|     if (!tiny_gps.time.isValid() || !tiny_gps.date.isValid()) | ||||
|       return; | ||||
|     time::ESPTime val{}; | ||||
|     val.year = tiny_gps.date.year(); | ||||
|     val.month = tiny_gps.date.month(); | ||||
|     val.day_of_month = tiny_gps.date.day(); | ||||
|     val.hour = tiny_gps.time.hour(); | ||||
|     val.minute = tiny_gps.time.minute(); | ||||
|     val.second = tiny_gps.time.second(); | ||||
|     val.recalc_timestamp_utc(false); | ||||
|     this->synchronize_epoch_(val.timestamp); | ||||
|     this->has_time_ = true; | ||||
|   } | ||||
|   bool has_time_{false}; | ||||
| }; | ||||
|  | ||||
| }  // namespace gps | ||||
| }  // namespace esphome | ||||
| @@ -13,8 +13,8 @@ void HLW8012Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up HLW8012..."); | ||||
|   this->sel_pin_->setup(); | ||||
|   this->sel_pin_->digital_write(this->current_mode_); | ||||
|   this->cf_store_.setup(this->cf_pin_); | ||||
|   this->cf1_store_.setup(this->cf1_pin_); | ||||
|   this->cf_store_.pulse_counter_setup(this->cf_pin_); | ||||
|   this->cf1_store_.pulse_counter_setup(this->cf1_pin_); | ||||
| } | ||||
| void HLW8012Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "HLW8012:"); | ||||
| @@ -32,18 +32,18 @@ void HLW8012Component::dump_config() { | ||||
| float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
| void HLW8012Component::update() { | ||||
|   // HLW8012 has 50% duty cycle | ||||
|   const uint32_t last_rise_cf = this->cf_store_.get_last_rise(); | ||||
|   const uint32_t last_rise_cf1 = this->cf1_store_.get_last_rise(); | ||||
|   const uint32_t now = micros(); | ||||
|   float full_cycle_cf = this->cf_store_.get_pulse_width_s() * 2; | ||||
|   float full_cycle_cf1 = this->cf1_store_.get_pulse_width_s() * 2; | ||||
|   float cf_hz = 0.0f, cf1_hz = 0.0f; | ||||
|   auto update_interval_micros = static_cast<uint32_t>(this->update_interval_ * 1e3f); | ||||
|  | ||||
|   if (full_cycle_cf != 0.0f && now - last_rise_cf < update_interval_micros * 3) | ||||
|     cf_hz = 1.0f / full_cycle_cf; | ||||
|   if (full_cycle_cf1 != 0.0f && now - last_rise_cf1 < update_interval_micros * 3) | ||||
|     cf1_hz = 1.0f / full_cycle_cf1; | ||||
|   pulse_counter::pulse_counter_t raw_cf = this->cf_store_.read_raw_value(); | ||||
|   pulse_counter::pulse_counter_t raw_cf1 = this->cf1_store_.read_raw_value(); | ||||
|   float cf_hz = raw_cf / (this->get_update_interval() / 1000.0f); | ||||
|   if (raw_cf <= 1) { | ||||
|     // don't count single pulse as power | ||||
|     cf_hz = 0.0f; | ||||
|   } | ||||
|   float cf1_hz = raw_cf1 / (this->get_update_interval() / 1000.0f); | ||||
|   if (raw_cf1 <= 1) { | ||||
|     // don't count single pulse as anything | ||||
|     cf1_hz = 0.0f; | ||||
|   } | ||||
|  | ||||
|   if (this->nth_value_++ < 2) { | ||||
|     return; | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/esphal.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/pulse_width/pulse_width.h" | ||||
| #include "esphome/components/pulse_counter/pulse_counter_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace hlw8012 { | ||||
| @@ -34,9 +34,9 @@ class HLW8012Component : public PollingComponent { | ||||
|   float voltage_divider_{2351}; | ||||
|   GPIOPin *sel_pin_; | ||||
|   GPIOPin *cf_pin_; | ||||
|   pulse_width::PulseWidthSensorStore cf_store_; | ||||
|   pulse_counter::PulseCounterStorage cf_store_; | ||||
|   GPIOPin *cf1_pin_; | ||||
|   pulse_width::PulseWidthSensorStore cf1_store_; | ||||
|   pulse_counter::PulseCounterStorage cf1_store_; | ||||
|   sensor::Sensor *voltage_sensor_{nullptr}; | ||||
|   sensor::Sensor *current_sensor_{nullptr}; | ||||
|   sensor::Sensor *power_sensor_{nullptr}; | ||||
|   | ||||
| @@ -6,7 +6,7 @@ from esphome.const import CONF_CHANGE_MODE_EVERY, 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 | ||||
|  | ||||
| AUTO_LOAD = ['pulse_width'] | ||||
| AUTO_LOAD = ['pulse_counter'] | ||||
|  | ||||
| hlw8012_ns = cg.esphome_ns.namespace('hlw8012') | ||||
| HLW8012Component = hlw8012_ns.class_('HLW8012Component', cg.PollingComponent) | ||||
|   | ||||
| @@ -163,6 +163,14 @@ class I2CDevice { | ||||
|    */ | ||||
|   bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0);  // NOLINT | ||||
|  | ||||
|   template<size_t N> optional<std::array<uint8_t, N>> read_bytes(uint8_t a_register) {  // NOLINT | ||||
|     std::array<uint8_t, N> res; | ||||
|     if (!this->read_bytes(a_register, res.data(), N)) { | ||||
|       return {}; | ||||
|     } | ||||
|     return res; | ||||
|   } | ||||
|  | ||||
|   /** Read len amount of 16-bit words (MSB first) from a register into data. | ||||
|    * | ||||
|    * @param a_register The register number to write to the bus before reading. | ||||
| @@ -176,6 +184,13 @@ class I2CDevice { | ||||
|   /// Read a single byte from a register into the data variable. Return true if successful. | ||||
|   bool read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion = 0);  // NOLINT | ||||
|  | ||||
|   optional<uint8_t> read_byte(uint8_t a_register) {  // NOLINT | ||||
|     uint8_t data; | ||||
|     if (!this->read_byte(a_register, &data)) | ||||
|       return {}; | ||||
|     return data; | ||||
|   } | ||||
|  | ||||
|   /// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful. | ||||
|   bool read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion = 0);  // NOLINT | ||||
|  | ||||
| @@ -188,6 +203,20 @@ class I2CDevice { | ||||
|    */ | ||||
|   bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len);  // NOLINT | ||||
|  | ||||
|   /** Write a vector of data to a register. | ||||
|    * | ||||
|    * @param a_register The register to write to. | ||||
|    * @param data The data to write. | ||||
|    * @return If the operation was successful. | ||||
|    */ | ||||
|   bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) {  // NOLINT | ||||
|     return this->write_bytes(a_register, data.data(), data.size()); | ||||
|   } | ||||
|  | ||||
|   template<size_t N> bool write_bytes(uint8_t a_register, const std::array<uint8_t, N> &data) {  // NOLINT | ||||
|     return this->write_bytes(a_register, data.data(), data.size()); | ||||
|   } | ||||
|  | ||||
|   /** Write len amount of 16-bit words (MSB first) to the specified register. | ||||
|    * | ||||
|    * @param a_register The register to write the values to. | ||||
|   | ||||
| @@ -33,7 +33,7 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({ | ||||
|     cv.Required(CONF_TIME_UNIT): cv.enum(INTEGRATION_TIMES, lower=True), | ||||
|     cv.Optional(CONF_INTEGRATION_METHOD, default='trapezoid'): | ||||
|         cv.enum(INTEGRATION_METHODS, lower=True), | ||||
|     cv.Optional(CONF_RESTORE, default=True): cv.boolean, | ||||
|     cv.Optional(CONF_RESTORE, default=False): cv.boolean, | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,20 +1,54 @@ | ||||
| import math | ||||
|  | ||||
| from esphome import pins | ||||
| from esphome.components import output | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
| from esphome.const import APB_CLOCK_FREQ, CONF_BIT_DEPTH, CONF_CHANNEL, CONF_FREQUENCY, \ | ||||
| from esphome.const import CONF_BIT_DEPTH, CONF_CHANNEL, CONF_FREQUENCY, \ | ||||
|     CONF_ID, CONF_PIN, ESP_PLATFORM_ESP32 | ||||
|  | ||||
| ESP_PLATFORMS = [ESP_PLATFORM_ESP32] | ||||
|  | ||||
|  | ||||
| def calc_max_frequency(bit_depth): | ||||
|     return 80e6 / (2**bit_depth) | ||||
|  | ||||
|  | ||||
| def calc_min_frequency(bit_depth): | ||||
|     # LEDC_DIV_NUM_HSTIMER is 15-bit unsigned integer | ||||
|     # lower 8 bits represent fractional part | ||||
|     max_div_num = ((1 << 16) - 1) / 256.0 | ||||
|     return 80e6 / (max_div_num * (2**bit_depth)) | ||||
|  | ||||
|  | ||||
| def validate_frequency_bit_depth(obj): | ||||
|     frequency = obj[CONF_FREQUENCY] | ||||
|     if CONF_BIT_DEPTH not in obj: | ||||
|         obj = obj.copy() | ||||
|         for bit_depth in range(15, 0, -1): | ||||
|             if calc_min_frequency(bit_depth) <= frequency <= calc_max_frequency(bit_depth): | ||||
|                 obj[CONF_BIT_DEPTH] = bit_depth | ||||
|                 break | ||||
|         else: | ||||
|             min_freq = min(calc_min_frequency(x) for x in range(1, 16)) | ||||
|             max_freq = max(calc_max_frequency(x) for x in range(1, 16)) | ||||
|             if frequency < min_freq: | ||||
|                 raise cv.Invalid("This frequency setting is not possible, please choose a higher " | ||||
|                                  "frequency (at least {}Hz)".format(int(min_freq))) | ||||
|             if frequency > max_freq: | ||||
|                 raise cv.Invalid("This frequency setting is not possible, please choose a lower " | ||||
|                                  "frequency (at most {}Hz)".format(int(max_freq))) | ||||
|             raise cv.Invalid("Invalid frequency!") | ||||
|  | ||||
|     bit_depth = obj[CONF_BIT_DEPTH] | ||||
|     max_freq = APB_CLOCK_FREQ / (2**bit_depth) | ||||
|     min_freq = calc_min_frequency(bit_depth) | ||||
|     max_freq = calc_max_frequency(bit_depth) | ||||
|     if frequency > max_freq: | ||||
|         raise cv.Invalid('Maximum frequency for bit depth {} is {}Hz'.format(bit_depth, max_freq)) | ||||
|         raise cv.Invalid('Maximum frequency for bit depth {} is {}Hz. Please decrease the ' | ||||
|                          'bit_depth.'.format(bit_depth, int(math.floor(max_freq)))) | ||||
|     if frequency < calc_min_frequency(bit_depth): | ||||
|         raise cv.Invalid('Minimum frequency for bit depth {} is {}Hz. Please increase the ' | ||||
|                          'bit_depth.'.format(bit_depth, int(math.ceil(min_freq)))) | ||||
|     return obj | ||||
|  | ||||
|  | ||||
| @@ -25,8 +59,8 @@ CONFIG_SCHEMA = cv.All(output.FLOAT_OUTPUT_SCHEMA.extend({ | ||||
|     cv.Required(CONF_ID): cv.declare_id(LEDCOutput), | ||||
|     cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, | ||||
|     cv.Optional(CONF_FREQUENCY, default='1kHz'): cv.frequency, | ||||
|     cv.Optional(CONF_BIT_DEPTH, default=12): cv.All(cv.int_, cv.Range(min=1, max=15)), | ||||
|     cv.Optional(CONF_CHANNEL): cv.All(cv.int_, cv.Range(min=0, max=15)) | ||||
|     cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=1, max=15), | ||||
|     cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15), | ||||
| }).extend(cv.COMPONENT_SCHEMA), validate_frequency_bit_depth) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import mqtt | ||||
| 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_INTERNAL, CONF_NAME, CONF_MQTT_ID, CONF_POWER_SUPPLY | ||||
| from esphome.core import coroutine, coroutine_with_priority | ||||
| from .automation import light_control_to_code  # noqa | ||||
| from .effects import validate_effects, BINARY_EFFECTS, \ | ||||
| @@ -35,6 +35,7 @@ ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(AddressableLightState), | ||||
|     cv.Optional(CONF_EFFECTS): validate_effects(ADDRESSABLE_EFFECTS), | ||||
|     cv.Optional(CONF_COLOR_CORRECT): cv.All([cv.percentage], cv.Length(min=3, max=4)), | ||||
|     cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply), | ||||
| }) | ||||
|  | ||||
|  | ||||
| @@ -52,6 +53,10 @@ def setup_light_core_(light_var, output_var, config): | ||||
|     if CONF_COLOR_CORRECT in config: | ||||
|         cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT])) | ||||
|  | ||||
|     if CONF_POWER_SUPPLY in config: | ||||
|         var_ = yield cg.get_variable(config[CONF_POWER_SUPPLY]) | ||||
|         cg.add(output_var.set_power_supply(var_)) | ||||
|  | ||||
|     if CONF_MQTT_ID in config: | ||||
|         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], light_var) | ||||
|         yield mqtt.register_mqtt_component(mqtt_, config) | ||||
|   | ||||
| @@ -1,9 +1,14 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "light_output.h" | ||||
| #include "light_state.h" | ||||
|  | ||||
| #ifdef USE_POWER_SUPPLY | ||||
| #include "esphome/components/power_supply/power_supply.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace light { | ||||
|  | ||||
| @@ -30,6 +35,7 @@ struct ESPColor { | ||||
|       }; | ||||
|     }; | ||||
|     uint8_t raw[4]; | ||||
|     uint32_t raw_32; | ||||
|   }; | ||||
|   inline ESPColor() ALWAYS_INLINE : r(0), g(0), b(0), w(0) {}  // NOLINT | ||||
|   inline ESPColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) ALWAYS_INLINE : r(red), | ||||
| @@ -47,7 +53,7 @@ struct ESPColor { | ||||
|     this->b = rhs.b; | ||||
|     this->w = rhs.w; | ||||
|   } | ||||
|   inline bool is_on() ALWAYS_INLINE { return this->r != 0 || this->g != 0 || this->b != 0 || this->w != 0; } | ||||
|   inline bool is_on() ALWAYS_INLINE { return this->raw_32 != 0; } | ||||
|   inline ESPColor &operator=(const ESPColor &rhs) ALWAYS_INLINE { | ||||
|     this->r = rhs.r; | ||||
|     this->g = rhs.g; | ||||
| @@ -527,14 +533,32 @@ class AddressableLight : public LightOutput { | ||||
|   void setup_state(LightState *state) override { this->correction_.calculate_gamma_table(state->get_gamma_correct()); } | ||||
|   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 | ||||
|  | ||||
|  protected: | ||||
|   bool should_show_() const { return this->effect_active_ || this->next_show_; } | ||||
|   void mark_shown_() { this->next_show_ = false; } | ||||
|   void mark_shown_() { | ||||
|     this->next_show_ = false; | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|     for (auto c : *this) { | ||||
|       if (c.get().is_on()) { | ||||
|         this->power_.request(); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|     this->power_.unrequest(); | ||||
| #endif | ||||
|   } | ||||
|   virtual ESPColorView get_view_internal(int32_t index) const = 0; | ||||
|  | ||||
|   bool effect_active_{false}; | ||||
|   bool next_show_{true}; | ||||
|   ESPColorCorrection correction_{}; | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|   power_supply::PowerSupplyRequester power_; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| }  // namespace light | ||||
|   | ||||
| @@ -112,7 +112,7 @@ def to_code(config): | ||||
|  | ||||
|     if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose: | ||||
|         debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)] | ||||
|         cg.add_build_flag("-DDEBUG_ESP_PORT{}".format(debug_serial_port)) | ||||
|         cg.add_build_flag("-DDEBUG_ESP_PORT={}".format(debug_serial_port)) | ||||
|         cg.add_build_flag("-DLWIP_DEBUG") | ||||
|         DEBUG_COMPONENTS = { | ||||
|             'HTTP_CLIENT', | ||||
|   | ||||
| @@ -12,8 +12,8 @@ MAX7219ComponentRef = MAX7219Component.operator('ref') | ||||
| CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(MAX7219Component), | ||||
|  | ||||
|     cv.Optional(CONF_NUM_CHIPS, default=1): cv.All(cv.uint8_t, cv.Range(min=1)), | ||||
|     cv.Optional(CONF_INTENSITY, default=15): cv.All(cv.uint8_t, cv.Range(min=0, max=15)), | ||||
|     cv.Optional(CONF_NUM_CHIPS, default=1): cv.int_range(min=1, max=255), | ||||
|     cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15), | ||||
| }).extend(cv.polling_component_schema('1s')).extend(spi.SPI_DEVICE_SCHEMA) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -110,7 +110,7 @@ void MPU6050Component::update() { | ||||
|   float accel_y = data[1] * MPU6050_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; | ||||
|   float accel_z = data[2] * MPU6050_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; | ||||
|  | ||||
|   float temperature = raw_data[3] / 340.0f + 36.53f; | ||||
|   float temperature = data[3] / 340.0f + 36.53f; | ||||
|  | ||||
|   float gyro_x = data[4] * MPU6050_SCALE_DPS_PER_DIGIT_2000; | ||||
|   float gyro_y = data[5] * MPU6050_SCALE_DPS_PER_DIGIT_2000; | ||||
|   | ||||
| @@ -24,12 +24,12 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC | ||||
|   JsonArray &modes = root.createNestedArray("modes"); | ||||
|   // sort array for nice UI in HA | ||||
|   if (traits.supports_mode(CLIMATE_MODE_AUTO)) | ||||
|     modes.add("auto"); | ||||
|   modes.add("off"); | ||||
|     modes.add(climate_mode_to_string(CLIMATE_MODE_AUTO)); | ||||
|   modes.add(climate_mode_to_string(CLIMATE_MODE_OFF)); | ||||
|   if (traits.supports_mode(CLIMATE_MODE_COOL)) | ||||
|     modes.add("cool"); | ||||
|     modes.add(climate_mode_to_string(CLIMATE_MODE_COOL)); | ||||
|   if (traits.supports_mode(CLIMATE_MODE_HEAT)) | ||||
|     modes.add("heat"); | ||||
|     modes.add(climate_mode_to_string(CLIMATE_MODE_HEAT)); | ||||
|  | ||||
|   if (traits.get_supports_two_point_target_temperature()) { | ||||
|     // temperature_low_command_topic | ||||
| @@ -60,6 +60,8 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC | ||||
|     // away_mode_state_topic | ||||
|     root["away_mode_state_topic"] = this->get_away_state_topic(); | ||||
|   } | ||||
|   config.state_topic = false; | ||||
|   config.command_topic = false; | ||||
| } | ||||
| void MQTTClimateComponent::setup() { | ||||
|   auto traits = this->device_->get_traits(); | ||||
| @@ -144,7 +146,7 @@ bool MQTTClimateComponent::publish_state_() { | ||||
|   if (!this->publish(this->get_mode_state_topic(), mode_s)) | ||||
|     success = false; | ||||
|   int8_t accuracy = traits.get_temperature_accuracy_decimals(); | ||||
|   if (traits.get_supports_current_temperature()) { | ||||
|   if (traits.get_supports_current_temperature() && !isnan(this->device_->current_temperature)) { | ||||
|     std::string payload = value_accuracy_to_string(this->device_->current_temperature, accuracy); | ||||
|     if (!this->publish(this->get_current_temperature_state_topic(), payload)) | ||||
|       success = false; | ||||
|   | ||||
| @@ -13,8 +13,8 @@ CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(MY9231OutputComponent), | ||||
|     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=6): cv.All(cv.int_, cv.Range(min=3, max=1020)), | ||||
|     cv.Optional(CONF_NUM_CHIPS, default=2): cv.All(cv.int_, cv.Range(min=1, max=255)), | ||||
|     cv.Optional(CONF_NUM_CHANNELS, default=6): cv.int_range(min=3, max=1020), | ||||
|     cv.Optional(CONF_NUM_CHIPS, default=2): cv.int_range(min=1, max=255), | ||||
|     cv.Optional(CONF_BIT_DEPTH, default=16): cv.one_of(8, 12, 14, 16, int=True), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/esphal.h" | ||||
| #include "esphome/components/output/float_output.h" | ||||
|  | ||||
| namespace esphome { | ||||
|   | ||||
| @@ -13,7 +13,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ | ||||
|     cv.GenerateID(CONF_MY9231_ID): cv.use_id(MY9231OutputComponent), | ||||
|  | ||||
|     cv.Required(CONF_ID): cv.declare_id(Channel), | ||||
|     cv.Required(CONF_CHANNEL): cv.All(cv.int_, cv.Range(min=0, max=65535)), | ||||
|     cv.Required(CONF_CHANNEL): cv.uint16_t, | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| from esphome.components import light, power_supply | ||||
| from esphome.components import light | ||||
| from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_METHOD, CONF_NUM_LEDS, CONF_PIN, \ | ||||
|     CONF_POWER_SUPPLY, CONF_TYPE, CONF_VARIANT, CONF_OUTPUT_ID | ||||
|     CONF_TYPE, CONF_VARIANT, CONF_OUTPUT_ID | ||||
| from esphome.core import CORE | ||||
|  | ||||
| neopixelbus_ns = cg.esphome_ns.namespace('neopixelbus') | ||||
| @@ -138,8 +138,6 @@ CONFIG_SCHEMA = cv.All(light.ADDRESSABLE_LIGHT_SCHEMA.extend({ | ||||
|     cv.Optional(CONF_DATA_PIN): pins.output_pin, | ||||
|  | ||||
|     cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, | ||||
|  | ||||
|     cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply), | ||||
| }).extend(cv.COMPONENT_SCHEMA), validate, validate_method_pin) | ||||
|  | ||||
|  | ||||
| @@ -162,8 +160,4 @@ def to_code(config): | ||||
|  | ||||
|     cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE]))) | ||||
|  | ||||
|     if CONF_POWER_SUPPLY in config: | ||||
|         var_ = yield cg.get_variable(config[CONF_POWER_SUPPLY]) | ||||
|         cg.add(var.set_power_supply(var_)) | ||||
|  | ||||
|     cg.add_library('NeoPixelBus', '2.4.1') | ||||
|   | ||||
| @@ -9,10 +9,6 @@ | ||||
| #error The NeoPixelBus library requires at least arduino_core_version 2.4.x | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_POWER_SUPPLY | ||||
| #include "esphome/components/power_supply/power_supply.h" | ||||
| #endif | ||||
|  | ||||
| #include "NeoPixelBus.h" | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -54,10 +50,6 @@ enum class ESPNeoPixelOrder { | ||||
| template<typename T_METHOD, typename T_COLOR_FEATURE> | ||||
| class NeoPixelBusLightOutputBase : public Component, public light::AddressableLight { | ||||
|  public: | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|   void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_supply_ = power_supply; } | ||||
| #endif | ||||
|  | ||||
|   NeoPixelBus<T_COLOR_FEATURE, T_METHOD> *get_controller() const { return this->controller_; } | ||||
|  | ||||
|   void clear_effect_data() override { | ||||
| @@ -95,27 +87,6 @@ class NeoPixelBusLightOutputBase : public Component, public light::AddressableLi | ||||
|     this->mark_shown_(); | ||||
|     this->controller_->Dirty(); | ||||
|  | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|     if (this->power_supply_ != nullptr) { | ||||
|       bool is_light_on = false; | ||||
|       for (int i = 0; i < this->size(); i++) { | ||||
|         if ((*this)[i].get().is_on()) { | ||||
|           is_light_on = true; | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (is_light_on && !this->has_requested_high_power_) { | ||||
|         this->power_supply_->request_high_power(); | ||||
|         this->has_requested_high_power_ = true; | ||||
|       } | ||||
|       if (!is_light_on && this->has_requested_high_power_) { | ||||
|         this->power_supply_->unrequest_high_power(); | ||||
|         this->has_requested_high_power_ = false; | ||||
|       } | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     this->controller_->Show(); | ||||
|   } | ||||
|  | ||||
| @@ -135,10 +106,6 @@ class NeoPixelBusLightOutputBase : public Component, public light::AddressableLi | ||||
|   NeoPixelBus<T_COLOR_FEATURE, T_METHOD> *controller_{nullptr}; | ||||
|   uint8_t *effect_data_{nullptr}; | ||||
|   uint8_t rgb_offsets_[4]{0, 1, 2, 3}; | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|   power_supply::PowerSupply *power_supply_{nullptr}; | ||||
|   bool has_requested_high_power_{false}; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| template<typename T_METHOD, typename T_COLOR_FEATURE = NeoRgbFeature> | ||||
|   | ||||
| @@ -4,7 +4,6 @@ | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/util.h" | ||||
| //#include "esphome/components/status_led.h" | ||||
|  | ||||
| #include <cstdio> | ||||
| #include <MD5Builder.h> | ||||
|   | ||||
| @@ -27,16 +27,13 @@ class BinaryOutput { | ||||
|    * | ||||
|    * @param power_supply The PowerSupplyComponent, set this to nullptr to disable the power supply. | ||||
|    */ | ||||
|   void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_supply_ = power_supply; } | ||||
|   void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); } | ||||
| #endif | ||||
|  | ||||
|   /// Enable this binary output. | ||||
|   virtual void turn_on() { | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|     if (this->power_supply_ != nullptr && !this->has_requested_high_power_) { | ||||
|       this->power_supply_->request_high_power(); | ||||
|       this->has_requested_high_power_ = true; | ||||
|     } | ||||
|     this->power_.request(); | ||||
| #endif | ||||
|     this->write_state(!this->inverted_); | ||||
|   } | ||||
| @@ -44,10 +41,7 @@ class BinaryOutput { | ||||
|   /// Disable this binary output. | ||||
|   virtual void turn_off() { | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|     if (this->power_supply_ != nullptr && this->has_requested_high_power_) { | ||||
|       this->power_supply_->unrequest_high_power(); | ||||
|       this->has_requested_high_power_ = false; | ||||
|     } | ||||
|     this->power_.unrequest(); | ||||
| #endif | ||||
|     this->write_state(this->inverted_); | ||||
|   } | ||||
| @@ -62,8 +56,7 @@ class BinaryOutput { | ||||
|  | ||||
|   bool inverted_{false}; | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|   power_supply::PowerSupply *power_supply_{nullptr}; | ||||
|   bool has_requested_high_power_{false}; | ||||
|   power_supply::PowerSupplyRequester power_{}; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -24,15 +24,9 @@ void FloatOutput::set_level(float state) { | ||||
|  | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|   if (state > 0.0f) {  // ON | ||||
|     if (this->power_supply_ != nullptr && !this->has_requested_high_power_) { | ||||
|       this->power_supply_->request_high_power(); | ||||
|       this->has_requested_high_power_ = true; | ||||
|     } | ||||
|     this->power_.request(); | ||||
|   } else {  // OFF | ||||
|     if (this->power_supply_ != nullptr && this->has_requested_high_power_) { | ||||
|       this->power_supply_->unrequest_high_power(); | ||||
|       this->has_requested_high_power_ = false; | ||||
|     } | ||||
|     this->power_.unrequest(); | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ | ||||
|     cv.Required(CONF_ID): cv.declare_id(PCA9685Channel), | ||||
|     cv.GenerateID(CONF_PCA9685_ID): cv.use_id(PCA9685Output), | ||||
|  | ||||
|     cv.Required(CONF_CHANNEL): cv.All(cv.Coerce(int), cv.Range(min=0, max=15)), | ||||
|     cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=15), | ||||
| }) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -132,14 +132,20 @@ void PMSX003Component::parse_data_() { | ||||
|       break; | ||||
|     } | ||||
|     case PMSX003_TYPE_5003ST: { | ||||
|       uint16_t pm_1_0_concentration = this->get_16_bit_uint_(10); | ||||
|       uint16_t pm_2_5_concentration = this->get_16_bit_uint_(12); | ||||
|       uint16_t pm_10_0_concentration = this->get_16_bit_uint_(14); | ||||
|       uint16_t formaldehyde = this->get_16_bit_uint_(28); | ||||
|       float temperature = this->get_16_bit_uint_(30) / 10.0f; | ||||
|       float humidity = this->get_16_bit_uint_(32) / 10.0f; | ||||
|       ESP_LOGD(TAG, "Got PM2.5 Concentration: %u µg/m^3, Temperature: %.1f°C, Humidity: %.1f%% Formaldehyde: %u µg/m^3", | ||||
|                pm_2_5_concentration, temperature, humidity, formaldehyde); | ||||
|       if (this->pm_1_0_sensor_ != nullptr) | ||||
|         this->pm_1_0_sensor_->publish_state(pm_1_0_concentration); | ||||
|       if (this->pm_2_5_sensor_ != nullptr) | ||||
|         this->pm_2_5_sensor_->publish_state(pm_2_5_concentration); | ||||
|       if (this->pm_10_0_sensor_ != nullptr) | ||||
|         this->pm_10_0_sensor_->publish_state(pm_10_0_concentration); | ||||
|       if (this->temperature_sensor_ != nullptr) | ||||
|         this->temperature_sensor_->publish_state(temperature); | ||||
|       if (this->humidity_sensor_ != nullptr) | ||||
|   | ||||
| @@ -24,9 +24,9 @@ PMSX003_TYPES = { | ||||
| } | ||||
|  | ||||
| SENSORS_TO_TYPE = { | ||||
|     CONF_PM_1_0: [CONF_PMSX003], | ||||
|     CONF_PM_1_0: [CONF_PMSX003, CONF_PMS5003ST], | ||||
|     CONF_PM_2_5: [CONF_PMSX003, CONF_PMS5003T, CONF_PMS5003ST], | ||||
|     CONF_PM_10_0: [CONF_PMSX003], | ||||
|     CONF_PM_10_0: [CONF_PMSX003, CONF_PMS5003ST], | ||||
|     CONF_TEMPERATURE: [CONF_PMS5003T, CONF_PMS5003ST], | ||||
|     CONF_HUMIDITY: [CONF_PMS5003T, CONF_PMS5003ST], | ||||
|     CONF_FORMALDEHYDE: [CONF_PMS5003ST], | ||||
|   | ||||
| @@ -42,7 +42,7 @@ void PowerSupply::request_high_power() { | ||||
| void PowerSupply::unrequest_high_power() { | ||||
|   this->active_requests_--; | ||||
|   if (this->active_requests_ < 0) { | ||||
|     // if asserts are disabled we're just going to use 0 as our now counter. | ||||
|     // we're just going to use 0 as our now counter. | ||||
|     this->active_requests_ = 0; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -39,5 +39,26 @@ class PowerSupply : public Component { | ||||
|   int16_t active_requests_{0};  // use signed integer to make catching negative requests easier. | ||||
| }; | ||||
|  | ||||
| class PowerSupplyRequester { | ||||
|  public: | ||||
|   void set_parent(PowerSupply *parent) { parent_ = parent; } | ||||
|   void request() { | ||||
|     if (!this->requested_ && this->parent_ != nullptr) { | ||||
|       this->parent_->request_high_power(); | ||||
|       this->requested_ = true; | ||||
|     } | ||||
|   } | ||||
|   void unrequest() { | ||||
|     if (this->requested_ && this->parent_ != nullptr) { | ||||
|       this->parent_->unrequest_high_power(); | ||||
|       this->requested_ = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   PowerSupply *parent_{nullptr}; | ||||
|   bool requested_{false}; | ||||
| }; | ||||
|  | ||||
| }  // namespace power_supply | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -28,7 +28,9 @@ void ICACHE_RAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| bool PulseCounterStorage::pulse_counter_setup() { | ||||
| bool PulseCounterStorage::pulse_counter_setup(GPIOPin *pin) { | ||||
|   this->pin = pin; | ||||
|   this->pin->setup(); | ||||
|   this->isr_pin = this->pin->to_isr(); | ||||
|   this->pin->attach_interrupt(PulseCounterStorage::gpio_intr, this, CHANGE); | ||||
|   return true; | ||||
| @@ -42,7 +44,9 @@ pulse_counter_t PulseCounterStorage::read_raw_value() { | ||||
| #endif | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
| bool PulseCounterStorage::pulse_counter_setup() { | ||||
| bool PulseCounterStorage::pulse_counter_setup(GPIOPin *pin) { | ||||
|   this->pin = pin; | ||||
|   this->pin->setup(); | ||||
|   this->pcnt_unit = next_pcnt_unit; | ||||
|   next_pcnt_unit = pcnt_unit_t(int(next_pcnt_unit) + 1);  // NOLINT | ||||
|  | ||||
| @@ -133,9 +137,7 @@ pulse_counter_t PulseCounterStorage::read_raw_value() { | ||||
|  | ||||
| void PulseCounterSensor::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str()); | ||||
|   this->pin_->setup(); | ||||
|   this->storage_.pin = this->pin_; | ||||
|   if (!this->storage_.pulse_counter_setup()) { | ||||
|   if (!this->storage_.pulse_counter_setup(this->pin_)) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   | ||||
| @@ -25,7 +25,7 @@ using pulse_counter_t = int32_t; | ||||
| #endif | ||||
|  | ||||
| struct PulseCounterStorage { | ||||
|   bool pulse_counter_setup(); | ||||
|   bool pulse_counter_setup(GPIOPin *pin); | ||||
|   pulse_counter_t read_raw_value(); | ||||
|  | ||||
|   static void gpio_intr(PulseCounterStorage *arg); | ||||
| @@ -42,9 +42,9 @@ struct PulseCounterStorage { | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
|   ISRInternalGPIOPin *isr_pin; | ||||
| #endif | ||||
|   PulseCounterCountMode rising_edge_mode{}; | ||||
|   PulseCounterCountMode falling_edge_mode{}; | ||||
|   uint32_t filter_us{}; | ||||
|   PulseCounterCountMode rising_edge_mode{PULSE_COUNTER_INCREMENT}; | ||||
|   PulseCounterCountMode falling_edge_mode{PULSE_COUNTER_DISABLE}; | ||||
|   uint32_t filter_us{0}; | ||||
|   pulse_counter_t last_value{0}; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ from esphome.components import binary_sensor | ||||
| from esphome.const import CONF_DATA, CONF_ID, 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_WAIT_TIME, CONF_TIMES, CONF_TYPE_ID, CONF_CARRIER_FREQUENCY | ||||
| from esphome.core import coroutine | ||||
| from esphome.py_compat import string_types, text_type | ||||
| from esphome.util import Registry, SimpleRegistry | ||||
| @@ -359,7 +359,9 @@ def raw_dumper(var, config): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @register_action('raw', RawAction, RAW_SCHEMA) | ||||
| @register_action('raw', RawAction, RAW_SCHEMA.extend({ | ||||
|     cv.Optional(CONF_CARRIER_FREQUENCY, default='0Hz'): cv.All(cv.frequency, cv.int_), | ||||
| })) | ||||
| def raw_action(var, config, args): | ||||
|     code_ = config[CONF_CODE] | ||||
|     if cg.is_template(code_): | ||||
| @@ -369,6 +371,8 @@ def raw_action(var, config, args): | ||||
|         code_ = config[CONF_CODE] | ||||
|         arr = cg.progmem_array(config[CONF_CODE_STORAGE_ID], code_) | ||||
|         cg.add(var.set_code_static(arr, len(code_))) | ||||
|     templ = yield cg.templatable(config[CONF_CARRIER_FREQUENCY], args, cg.uint32) | ||||
|     cg.add(var.set_carrier_frequency(templ)) | ||||
|  | ||||
|  | ||||
| # RC5 | ||||
| @@ -410,7 +414,7 @@ def rc5_action(var, config, args): | ||||
| RC_SWITCH_TIMING_SCHEMA = cv.All([cv.uint8_t], cv.Length(min=2, max=2)) | ||||
|  | ||||
| RC_SWITCH_PROTOCOL_SCHEMA = cv.Any( | ||||
|     cv.All(cv.Coerce(int), cv.Range(min=1, max=7)), | ||||
|     cv.int_range(min=1, max=7), | ||||
|     cv.Schema({ | ||||
|         cv.Required(CONF_PULSE_LENGTH): cv.uint32_t, | ||||
|         cv.Optional(CONF_SYNC, default=[1, 31]): RC_SWITCH_TIMING_SCHEMA, | ||||
| @@ -457,25 +461,32 @@ RC_SWITCH_TYPE_A_SCHEMA = cv.Schema({ | ||||
|     cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, | ||||
| }) | ||||
| RC_SWITCH_TYPE_B_SCHEMA = cv.Schema({ | ||||
|     cv.Required(CONF_ADDRESS): cv.All(cv.uint8_t, cv.Range(min=1, max=4)), | ||||
|     cv.Required(CONF_CHANNEL): cv.All(cv.uint8_t, cv.Range(min=1, max=4)), | ||||
|     cv.Required(CONF_ADDRESS): cv.int_range(min=1, max=4), | ||||
|     cv.Required(CONF_CHANNEL): cv.int_range(min=1, max=4), | ||||
|     cv.Required(CONF_STATE): cv.boolean, | ||||
|     cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, | ||||
| }) | ||||
| RC_SWITCH_TYPE_C_SCHEMA = cv.Schema({ | ||||
|     cv.Required(CONF_FAMILY): cv.one_of('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', | ||||
|                                         'l', 'm', 'n', 'o', 'p', lower=True), | ||||
|     cv.Required(CONF_GROUP): cv.All(cv.uint8_t, cv.Range(min=1, max=4)), | ||||
|     cv.Required(CONF_DEVICE): cv.All(cv.uint8_t, cv.Range(min=1, max=4)), | ||||
|     cv.Required(CONF_GROUP): cv.int_range(min=1, max=4), | ||||
|     cv.Required(CONF_DEVICE): cv.int_range(min=1, max=4), | ||||
|     cv.Required(CONF_STATE): cv.boolean, | ||||
|     cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, | ||||
| }) | ||||
| RC_SWITCH_TYPE_D_SCHEMA = cv.Schema({ | ||||
|     cv.Required(CONF_GROUP): cv.one_of('a', 'b', 'c', 'd', lower=True), | ||||
|     cv.Required(CONF_DEVICE): cv.All(cv.uint8_t, cv.Range(min=1, max=3)), | ||||
|     cv.Required(CONF_DEVICE): cv.int_range(min=1, max=3), | ||||
|     cv.Required(CONF_STATE): cv.boolean, | ||||
|     cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, | ||||
| }) | ||||
| 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), | ||||
|     }), | ||||
| }) | ||||
|  | ||||
| rc_switch_protocols = ns.rc_switch_protocols | ||||
| RCSwitchBase = ns.class_('RCSwitchBase') | ||||
| @@ -494,7 +505,8 @@ def rc_switch_raw_binary_sensor(var, config): | ||||
|     cg.add(var.set_code(config[CONF_CODE])) | ||||
|  | ||||
|  | ||||
| @register_action('rc_switch_raw', RCSwitchRawAction, RC_SWITCH_RAW_SCHEMA) | ||||
| @register_action('rc_switch_raw', RCSwitchRawAction, | ||||
|                  RC_SWITCH_RAW_SCHEMA.extend(RC_SWITCH_TRANSMITTER)) | ||||
| def rc_switch_raw_action(var, config, args): | ||||
|     proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase, | ||||
|                                  to_exp=build_rc_switch_protocol) | ||||
| @@ -508,7 +520,8 @@ def rc_switch_type_a_binary_sensor(var, config): | ||||
|     cg.add(var.set_type_a(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE])) | ||||
|  | ||||
|  | ||||
| @register_action('rc_switch_type_a', RCSwitchTypeAAction, RC_SWITCH_TYPE_A_SCHEMA) | ||||
| @register_action('rc_switch_type_a', RCSwitchTypeAAction, | ||||
|                  RC_SWITCH_TYPE_A_SCHEMA.extend(RC_SWITCH_TRANSMITTER)) | ||||
| def rc_switch_type_a_action(var, config, args): | ||||
|     proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase, | ||||
|                                  to_exp=build_rc_switch_protocol) | ||||
| @@ -524,7 +537,8 @@ def rc_switch_type_b_binary_sensor(var, config): | ||||
|     cg.add(var.set_type_b(config[CONF_ADDRESS], config[CONF_CHANNEL], config[CONF_STATE])) | ||||
|  | ||||
|  | ||||
| @register_action('rc_switch_type_b', RCSwitchTypeBAction, RC_SWITCH_TYPE_B_SCHEMA) | ||||
| @register_action('rc_switch_type_b', RCSwitchTypeBAction, | ||||
|                  RC_SWITCH_TYPE_B_SCHEMA.extend(RC_SWITCH_TRANSMITTER)) | ||||
| def rc_switch_type_b_action(var, config, args): | ||||
|     proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase, | ||||
|                                  to_exp=build_rc_switch_protocol) | ||||
| @@ -541,7 +555,8 @@ def rc_switch_type_c_binary_sensor(var, config): | ||||
|                           config[CONF_STATE])) | ||||
|  | ||||
|  | ||||
| @register_action('rc_switch_type_c', RCSwitchTypeCAction, RC_SWITCH_TYPE_C_SCHEMA) | ||||
| @register_action('rc_switch_type_c', RCSwitchTypeCAction, | ||||
|                  RC_SWITCH_TYPE_C_SCHEMA.extend(RC_SWITCH_TRANSMITTER)) | ||||
| def rc_switch_type_c_action(var, config, args): | ||||
|     proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase, | ||||
|                                  to_exp=build_rc_switch_protocol) | ||||
| @@ -552,13 +567,15 @@ def rc_switch_type_c_action(var, config, args): | ||||
|     cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, bool)))) | ||||
|  | ||||
|  | ||||
| @register_binary_sensor('rc_switch_type_d', RCSwitchRawReceiver, RC_SWITCH_TYPE_D_SCHEMA) | ||||
| @register_binary_sensor('rc_switch_type_d', RCSwitchRawReceiver, | ||||
|                         RC_SWITCH_TYPE_D_SCHEMA.extend(RC_SWITCH_TRANSMITTER)) | ||||
| def rc_switch_type_d_binary_sensor(var, config): | ||||
|     cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) | ||||
|     cg.add(var.set_type_d(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE])) | ||||
|  | ||||
|  | ||||
| @register_action('rc_switch_type_d', RCSwitchTypeDAction, RC_SWITCH_TYPE_D_SCHEMA) | ||||
| @register_action('rc_switch_type_d', RCSwitchTypeDAction, | ||||
|                  RC_SWITCH_TYPE_D_SCHEMA.extend(RC_SWITCH_TRANSMITTER)) | ||||
| def rc_switch_type_d_action(var, config, args): | ||||
|     proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase, | ||||
|                                  to_exp=build_rc_switch_protocol) | ||||
|   | ||||
| @@ -6,7 +6,7 @@ namespace remote_base { | ||||
|  | ||||
| static const char *TAG = "remote.raw"; | ||||
|  | ||||
| void RawDumper::dump(RemoteReceiveData src) { | ||||
| bool RawDumper::dump(RemoteReceiveData src) { | ||||
|   char buffer[256]; | ||||
|   uint32_t buffer_offset = 0; | ||||
|   buffer_offset += sprintf(buffer, "Received Raw: "); | ||||
| @@ -16,7 +16,7 @@ void RawDumper::dump(RemoteReceiveData src) { | ||||
|     const uint32_t remaining_length = sizeof(buffer) - buffer_offset; | ||||
|     int written; | ||||
|  | ||||
|     if (i + 1 < src.size()) { | ||||
|     if (i + 1 < src.size() - 1) { | ||||
|       written = snprintf(buffer + buffer_offset, remaining_length, "%d, ", value); | ||||
|     } else { | ||||
|       written = snprintf(buffer + buffer_offset, remaining_length, "%d", value); | ||||
| @@ -40,6 +40,7 @@ void RawDumper::dump(RemoteReceiveData src) { | ||||
|   if (buffer_offset != 0) { | ||||
|     ESP_LOGD(TAG, "%s", buffer); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace remote_base | ||||
|   | ||||
| @@ -44,9 +44,9 @@ template<typename... Ts> class RawAction : public RemoteTransmitterActionBase<Ts | ||||
|     this->code_static_ = code; | ||||
|     this->code_static_len_ = len; | ||||
|   } | ||||
|   TEMPLATABLE_VALUE(uint32_t, carrier_frequency); | ||||
|  | ||||
|   void encode(RemoteTransmitData *dst, Ts... x) override { | ||||
|     // dst->set_data(data); | ||||
|     if (this->code_static_ != nullptr) { | ||||
|       for (size_t i = 0; i < this->code_static_len_; i++) { | ||||
|         auto val = this->code_static_[i]; | ||||
| @@ -58,6 +58,7 @@ template<typename... Ts> class RawAction : public RemoteTransmitterActionBase<Ts | ||||
|     } else { | ||||
|       dst->set_data(this->code_func_(x...)); | ||||
|     } | ||||
|     dst->set_carrier_frequency(this->carrier_frequency_.value(x...)); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
| @@ -68,7 +69,8 @@ template<typename... Ts> class RawAction : public RemoteTransmitterActionBase<Ts | ||||
|  | ||||
| class RawDumper : public RemoteReceiverDumperBase { | ||||
|  public: | ||||
|   void dump(RemoteReceiveData src) override; | ||||
|   bool dump(RemoteReceiveData src) override; | ||||
|   bool is_secondary() override { return true; } | ||||
| }; | ||||
|  | ||||
| }  // namespace remote_base | ||||
|   | ||||
| @@ -53,6 +53,7 @@ void RCSwitchBase::sync(RemoteTransmitData *dst) const { | ||||
|   } | ||||
| } | ||||
| void RCSwitchBase::transmit(RemoteTransmitData *dst, uint32_t code, uint8_t len) const { | ||||
|   dst->set_carrier_frequency(0); | ||||
|   for (int16_t i = len - 1; i >= 0; i--) { | ||||
|     if (code & (1 << i)) | ||||
|       this->one(dst); | ||||
| @@ -224,21 +225,25 @@ bool RCSwitchRawReceiver::matches(RemoteReceiveData src) { | ||||
|  | ||||
|   return decoded_nbits == this->nbits_ && decoded_code == this->code_; | ||||
| } | ||||
| void RCSwitchDumper::dump(RemoteReceiveData src) { | ||||
| bool RCSwitchDumper::dump(RemoteReceiveData src) { | ||||
|   for (uint8_t i = 1; i <= 7; i++) { | ||||
|     src.reset(); | ||||
|     uint32_t out_data; | ||||
|     uint8_t out_nbits; | ||||
|     RCSwitchBase *protocol = &rc_switch_protocols[i]; | ||||
|     if (protocol->decode(src, &out_data, &out_nbits)) { | ||||
|     if (protocol->decode(src, &out_data, &out_nbits) && out_nbits >= 3) { | ||||
|       char buffer[32]; | ||||
|       for (uint8_t j = 0; j < out_nbits; j++) | ||||
|         buffer[j] = (out_data & (1 << (out_nbits - j - 1))) ? '1' : '0'; | ||||
|  | ||||
|       buffer[out_nbits] = '\0'; | ||||
|       ESP_LOGD(TAG, "Received RCSwitch Raw: protocol=%u data='%s'", i, buffer); | ||||
|  | ||||
|       // only send first decoded protocol | ||||
|       return true; | ||||
|     } | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| }  // namespace remote_base | ||||
|   | ||||
| @@ -197,7 +197,7 @@ class RCSwitchRawReceiver : public RemoteReceiverBinarySensorBase { | ||||
|  | ||||
| class RCSwitchDumper : public RemoteReceiverDumperBase { | ||||
|  public: | ||||
|   void dump(RemoteReceiveData src) override; | ||||
|   bool dump(RemoteReceiveData src) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace remote_base | ||||
|   | ||||
| @@ -212,14 +212,21 @@ class RemoteReceiverListener { | ||||
|  | ||||
| class RemoteReceiverDumperBase { | ||||
|  public: | ||||
|   virtual void dump(RemoteReceiveData src) = 0; | ||||
|   virtual bool dump(RemoteReceiveData src) = 0; | ||||
|   virtual bool is_secondary() { return false; } | ||||
| }; | ||||
|  | ||||
| class RemoteReceiverBase : public RemoteComponentBase { | ||||
|  public: | ||||
|   RemoteReceiverBase(GPIOPin *pin) : RemoteComponentBase(pin) {} | ||||
|   void register_listener(RemoteReceiverListener *listener) { this->listeners_.push_back(listener); } | ||||
|   void register_dumper(RemoteReceiverDumperBase *dumper) { this->dumpers_.push_back(dumper); } | ||||
|   void register_dumper(RemoteReceiverDumperBase *dumper) { | ||||
|     if (dumper->is_secondary()) { | ||||
|       this->secondary_dumpers_.push_back(dumper); | ||||
|     } else { | ||||
|       this->dumpers_.push_back(dumper); | ||||
|     } | ||||
|   } | ||||
|   void set_tolerance(uint8_t tolerance) { tolerance_ = tolerance; } | ||||
|  | ||||
|  protected: | ||||
| @@ -233,9 +240,17 @@ class RemoteReceiverBase : public RemoteComponentBase { | ||||
|     return success; | ||||
|   } | ||||
|   void call_dumpers_() { | ||||
|     bool success = false; | ||||
|     for (auto *dumper : this->dumpers_) { | ||||
|       auto data = RemoteReceiveData(&this->temp_, this->tolerance_); | ||||
|       dumper->dump(data); | ||||
|       if (dumper->dump(data)) | ||||
|         success = true; | ||||
|     } | ||||
|     if (!success) { | ||||
|       for (auto *dumper : this->secondary_dumpers_) { | ||||
|         auto data = RemoteReceiveData(&this->temp_, this->tolerance_); | ||||
|         dumper->dump(data); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   void call_listeners_dumpers_() { | ||||
| @@ -247,6 +262,7 @@ class RemoteReceiverBase : public RemoteComponentBase { | ||||
|  | ||||
|   std::vector<RemoteReceiverListener *> listeners_; | ||||
|   std::vector<RemoteReceiverDumperBase *> dumpers_; | ||||
|   std::vector<RemoteReceiverDumperBase *> secondary_dumpers_; | ||||
|   std::vector<int32_t> temp_; | ||||
|   uint8_t tolerance_{25}; | ||||
| }; | ||||
| @@ -323,12 +339,13 @@ template<typename... Ts> class RemoteTransmitterActionBase : public Action<Ts... | ||||
|  | ||||
| template<typename T, typename D> class RemoteReceiverDumper : public RemoteReceiverDumperBase { | ||||
|  public: | ||||
|   void dump(RemoteReceiveData src) override { | ||||
|   bool dump(RemoteReceiveData src) override { | ||||
|     auto proto = T(); | ||||
|     auto decoded = proto.decode(src); | ||||
|     if (!decoded.has_value()) | ||||
|       return; | ||||
|       return false; | ||||
|     proto.dump(*decoded); | ||||
|     return true; | ||||
|   } | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -53,6 +53,10 @@ void RemoteReceiverComponent::setup() { | ||||
| void RemoteReceiverComponent::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Remote Receiver:"); | ||||
|   LOG_PIN("  Pin: ", this->pin_); | ||||
|   if (this->pin_->digital_read()) { | ||||
|     ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to " | ||||
|                   "invert the signal using 'inverted: True' in the pin schema!"); | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  Channel: %d", this->channel_); | ||||
|   ESP_LOGCONFIG(TAG, "  Clock divider: %u", this->clock_divider_); | ||||
|   ESP_LOGCONFIG(TAG, "  Tolerance: %u%%", this->tolerance_); | ||||
|   | ||||
| @@ -33,7 +33,7 @@ CONFIG_SCHEMA = cv.All(cv.Schema({ | ||||
|         sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 1), | ||||
|  | ||||
|     cv.Optional(CONF_RX_ONLY, default=False): cv.boolean, | ||||
|     cv.Optional(CONF_UPDATE_INTERVAL, default='0min'): cv.positive_time_period_minutes, | ||||
|     cv.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_minutes, | ||||
| }).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA), validate_sds011_rx_mode) | ||||
|  | ||||
|  | ||||
| @@ -42,7 +42,8 @@ def to_code(config): | ||||
|     yield cg.register_component(var, config) | ||||
|     yield uart.register_uart_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_update_interval_min(config[CONF_UPDATE_INTERVAL])) | ||||
|     if CONF_UPDATE_INTERVAL in config: | ||||
|         cg.add(var.set_update_interval_min(config[CONF_UPDATE_INTERVAL])) | ||||
|     cg.add(var.set_rx_mode_only(config[CONF_RX_ONLY])) | ||||
|  | ||||
|     if CONF_PM_2_5 in config: | ||||
|   | ||||
| @@ -148,7 +148,7 @@ def exponential_moving_average_filter_to_code(config, filter_id): | ||||
|     yield cg.new_Pvariable(filter_id, config[CONF_ALPHA], config[CONF_SEND_EVERY]) | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register('lambda', LambdaFilter, cv.lambda_) | ||||
| @FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_lambda) | ||||
| def lambda_filter_to_code(config, filter_id): | ||||
|     lambda_ = yield cg.process_lambda(config, [(float, 'x')], | ||||
|                                       return_type=cg.optional.template(float)) | ||||
|   | ||||
| @@ -88,10 +88,8 @@ std::string Sensor::unique_id() { return ""; } | ||||
| void Sensor::internal_send_state_to_frontend(float state) { | ||||
|   this->has_state_ = true; | ||||
|   this->state = state; | ||||
|   if (this->filter_list_ != nullptr) { | ||||
|     ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state, | ||||
|              this->get_unit_of_measurement().c_str(), this->get_accuracy_decimals()); | ||||
|   } | ||||
|   ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state, | ||||
|            this->get_unit_of_measurement().c_str(), this->get_accuracy_decimals()); | ||||
|   this->callback_.call(state); | ||||
| } | ||||
| bool Sensor::has_state() const { return this->has_state_; } | ||||
|   | ||||
							
								
								
									
										103
									
								
								esphome/components/sun/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								esphome/components/sun/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| import esphome.codegen as cg | ||||
| 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 | ||||
|  | ||||
| sun_ns = cg.esphome_ns.namespace('sun') | ||||
|  | ||||
| Sun = sun_ns.class_('Sun') | ||||
| SunTrigger = sun_ns.class_('SunTrigger', cg.PollingComponent, automation.Trigger.template()) | ||||
| SunCondition = sun_ns.class_('SunCondition', automation.Condition) | ||||
|  | ||||
| CONF_SUN_ID = 'sun_id' | ||||
| CONF_LATITUDE = 'latitude' | ||||
| CONF_LONGITUDE = 'longitude' | ||||
| CONF_ELEVATION = 'elevation' | ||||
| CONF_ON_SUNRISE = 'on_sunrise' | ||||
| CONF_ON_SUNSET = 'on_sunset' | ||||
|  | ||||
| ELEVATION_MAP = { | ||||
|     'sunrise': 0.0, | ||||
|     'sunset': 0.0, | ||||
|     'civil': -6.0, | ||||
|     'nautical': -12.0, | ||||
|     'astronomical': -18.0, | ||||
| } | ||||
|  | ||||
|  | ||||
| def elevation(value): | ||||
|     if isinstance(value, str): | ||||
|         try: | ||||
|             value = ELEVATION_MAP[cv.one_of(*ELEVATION_MAP, lower=True, space='_')] | ||||
|         except cv.Invalid: | ||||
|             pass | ||||
|     value = cv.angle(value) | ||||
|     return cv.float_range(min=-180, max=180)(value) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(Sun), | ||||
|     cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), | ||||
|     cv.Required(CONF_LATITUDE): cv.float_range(min=-90, max=90), | ||||
|     cv.Required(CONF_LONGITUDE): cv.float_range(min=-180, max=180), | ||||
|  | ||||
|     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_ON_SUNSET): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SunTrigger), | ||||
|         cv.Optional(CONF_ELEVATION, default=0.0): elevation, | ||||
|     }), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     time_ = yield cg.get_variable(config[CONF_TIME_ID]) | ||||
|     cg.add(var.set_time(time_)) | ||||
|     cg.add(var.set_latitude(config[CONF_LATITUDE])) | ||||
|     cg.add(var.set_longitude(config[CONF_LONGITUDE])) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_SUNRISE, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) | ||||
|         yield cg.register_component(trigger, conf) | ||||
|         yield cg.register_parented(trigger, var) | ||||
|         cg.add(trigger.set_sunrise(True)) | ||||
|         cg.add(trigger.set_elevation(conf[CONF_ELEVATION])) | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_SUNSET, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) | ||||
|         yield cg.register_component(trigger, conf) | ||||
|         yield cg.register_parented(trigger, var) | ||||
|         cg.add(trigger.set_sunrise(False)) | ||||
|         cg.add(trigger.set_elevation(conf[CONF_ELEVATION])) | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|  | ||||
| @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), | ||||
| })) | ||||
| def sun_above_horizon_to_code(config, condition_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(condition_id, template_arg) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     templ = yield cg.templatable(config[CONF_ELEVATION], args, cg.double) | ||||
|     cg.add(var.set_elevation(templ)) | ||||
|     cg.add(var.set_above(True)) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @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), | ||||
| })) | ||||
| def sun_below_horizon_to_code(config, condition_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(condition_id, template_arg) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     templ = yield cg.templatable(config[CONF_ELEVATION], args, cg.double) | ||||
|     cg.add(var.set_elevation(templ)) | ||||
|     cg.add(var.set_above(False)) | ||||
|     yield var | ||||
							
								
								
									
										30
									
								
								esphome/components/sun/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/sun/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor | ||||
| from esphome.const import UNIT_DEGREES, ICON_WEATHER_SUNSET, CONF_ID, CONF_TYPE | ||||
| from .. import sun_ns, CONF_SUN_ID, Sun | ||||
|  | ||||
| DEPENDENCIES = ['sun'] | ||||
|  | ||||
| SunSensor = sun_ns.class_('SunSensor', sensor.Sensor, cg.PollingComponent) | ||||
| SensorType = sun_ns.enum('SensorType') | ||||
| TYPES = { | ||||
|     'elevation': SensorType.SUN_SENSOR_ELEVATION, | ||||
|     'azimuth': SensorType.SUN_SENSOR_AZIMUTH, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_DEGREES, ICON_WEATHER_SUNSET, 1).extend({ | ||||
|     cv.GenerateID(): cv.declare_id(SunSensor), | ||||
|     cv.GenerateID(CONF_SUN_ID): cv.use_id(Sun), | ||||
|     cv.Required(CONF_TYPE): cv.enum(TYPES, lower=True), | ||||
| }).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) | ||||
|  | ||||
|     cg.add(var.set_type(config[CONF_TYPE])) | ||||
|     paren = yield cg.get_variable(config[CONF_SUN_ID]) | ||||
|     cg.add(var.set_parent(paren)) | ||||
							
								
								
									
										12
									
								
								esphome/components/sun/sensor/sun_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/sun/sensor/sun_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #include "sun_sensor.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sun { | ||||
|  | ||||
| static const char *TAG = "sun.sensor"; | ||||
|  | ||||
| void SunSensor::dump_config() { LOG_SENSOR("", "Sun Sensor", this); } | ||||
|  | ||||
| }  // namespace sun | ||||
| }  // namespace esphome | ||||
							
								
								
									
										41
									
								
								esphome/components/sun/sensor/sun_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/sun/sensor/sun_sensor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sun/sun.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sun { | ||||
|  | ||||
| enum SensorType { | ||||
|   SUN_SENSOR_ELEVATION, | ||||
|   SUN_SENSOR_AZIMUTH, | ||||
| }; | ||||
|  | ||||
| class SunSensor : public sensor::Sensor, public PollingComponent { | ||||
|  public: | ||||
|   void set_parent(Sun *parent) { parent_ = parent; } | ||||
|   void set_type(SensorType type) { type_ = type; } | ||||
|   void dump_config() override; | ||||
|   void update() override { | ||||
|     double val; | ||||
|     switch (this->type_) { | ||||
|       case SUN_SENSOR_ELEVATION: | ||||
|         val = this->parent_->elevation(); | ||||
|         break; | ||||
|       case SUN_SENSOR_AZIMUTH: | ||||
|         val = this->parent_->azimuth(); | ||||
|         break; | ||||
|       default: | ||||
|         return; | ||||
|     } | ||||
|     this->publish_state(val); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   sun::Sun *parent_; | ||||
|   SensorType type_; | ||||
| }; | ||||
|  | ||||
| }  // namespace sun | ||||
| }  // namespace esphome | ||||
							
								
								
									
										168
									
								
								esphome/components/sun/sun.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								esphome/components/sun/sun.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,168 @@ | ||||
| #include "sun.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sun { | ||||
|  | ||||
| static const char *TAG = "sun"; | ||||
|  | ||||
| #undef PI | ||||
|  | ||||
| /* Usually, ESPHome uses single-precision floating point values | ||||
|  * because those tend to be accurate enough and are more efficient. | ||||
|  * | ||||
|  * However, some of the data in this class has to be quite accurate, so double is | ||||
|  * used everywhere. | ||||
|  */ | ||||
| static const double PI = 3.141592653589793; | ||||
| static const double TAU = 6.283185307179586; | ||||
| static const double TO_RADIANS = PI / 180.0; | ||||
| static const double TO_DEGREES = 180.0 / PI; | ||||
| static const double EARTH_TILT = 23.44 * TO_RADIANS; | ||||
|  | ||||
| optional<time::ESPTime> Sun::sunrise(double elevation) { | ||||
|   auto time = this->time_->now(); | ||||
|   if (!time.is_valid()) | ||||
|     return {}; | ||||
|   double sun_time = this->sun_time_for_elevation_(time.day_of_year, elevation, true); | ||||
|   if (isnan(sun_time)) | ||||
|     return {}; | ||||
|   uint32_t epoch = this->calc_epoch_(time, sun_time); | ||||
|   return time::ESPTime::from_epoch_local(epoch); | ||||
| } | ||||
| optional<time::ESPTime> Sun::sunset(double elevation) { | ||||
|   auto time = this->time_->now(); | ||||
|   if (!time.is_valid()) | ||||
|     return {}; | ||||
|   double sun_time = this->sun_time_for_elevation_(time.day_of_year, elevation, false); | ||||
|   if (isnan(sun_time)) | ||||
|     return {}; | ||||
|   uint32_t epoch = this->calc_epoch_(time, sun_time); | ||||
|   return time::ESPTime::from_epoch_local(epoch); | ||||
| } | ||||
| double Sun::elevation() { | ||||
|   auto time = this->current_sun_time_(); | ||||
|   if (isnan(time)) | ||||
|     return NAN; | ||||
|   return this->elevation_(time); | ||||
| } | ||||
| double Sun::azimuth() { | ||||
|   auto time = this->current_sun_time_(); | ||||
|   if (isnan(time)) | ||||
|     return NAN; | ||||
|   return this->azimuth_(time); | ||||
| } | ||||
| double Sun::sun_declination_(double sun_time) { | ||||
|   double n = sun_time - 1.0; | ||||
|   // maximum declination | ||||
|   const double tot = -sin(EARTH_TILT); | ||||
|  | ||||
|   // eccentricity of the earth's orbit (ellipse) | ||||
|   double eccentricity = 0.0167; | ||||
|  | ||||
|   // days since perihelion (January 3rd) | ||||
|   double days_since_perihelion = n - 2; | ||||
|   // days since december solstice (december 22) | ||||
|   double days_since_december_solstice = n + 10; | ||||
|   const double c = TAU / 365.24; | ||||
|   double v = cos(c * days_since_december_solstice + 2 * eccentricity * sin(c * days_since_perihelion)); | ||||
|   // Make sure value is in range (double error may lead to results slightly larger than 1) | ||||
|   double x = clamp(tot * v, 0, 1); | ||||
|   return asin(x); | ||||
| } | ||||
| double Sun::elevation_ratio_(double sun_time) { | ||||
|   double decl = this->sun_declination_(sun_time); | ||||
|   double hangle = this->hour_angle_(sun_time); | ||||
|   double a = sin(this->latitude_rad_()) * sin(decl); | ||||
|   double b = cos(this->latitude_rad_()) * cos(decl) * cos(hangle); | ||||
|   double val = clamp(a + b, -1.0, 1.0); | ||||
|   return val; | ||||
| } | ||||
| double Sun::latitude_rad_() { return this->latitude_ * TO_RADIANS; } | ||||
| double Sun::hour_angle_(double sun_time) { | ||||
|   double time_of_day = fmod(sun_time, 1.0) * 24.0; | ||||
|   return -PI * (time_of_day - 12) / 12; | ||||
| } | ||||
| double Sun::elevation_(double sun_time) { return this->elevation_rad_(sun_time) * TO_DEGREES; } | ||||
| double Sun::elevation_rad_(double sun_time) { return asin(this->elevation_ratio_(sun_time)); } | ||||
| double Sun::zenith_rad_(double sun_time) { return acos(this->elevation_ratio_(sun_time)); } | ||||
| double Sun::azimuth_rad_(double sun_time) { | ||||
|   double hangle = -this->hour_angle_(sun_time); | ||||
|   double decl = this->sun_declination_(sun_time); | ||||
|   double zen = this->zenith_rad_(sun_time); | ||||
|   double nom = cos(zen) * sin(this->latitude_rad_()) - sin(decl); | ||||
|   double denom = sin(zen) * cos(this->latitude_rad_()); | ||||
|   double v = clamp(nom / denom, -1.0, 1.0); | ||||
|   double az = PI - acos(v); | ||||
|   if (hangle > 0) | ||||
|     az = -az; | ||||
|   if (az < 0) | ||||
|     az += TAU; | ||||
|   return az; | ||||
| } | ||||
| double Sun::azimuth_(double sun_time) { return this->azimuth_rad_(sun_time) * TO_DEGREES; } | ||||
| double Sun::calc_sun_time_(const time::ESPTime &time) { | ||||
|   // Time as seen at 0° longitude | ||||
|   if (!time.is_valid()) | ||||
|     return NAN; | ||||
|  | ||||
|   double base = (time.day_of_year + time.hour / 24.0 + time.minute / 24.0 / 60.0 + time.second / 24.0 / 60.0 / 60.0); | ||||
|   // Add longitude correction | ||||
|   double add = this->longitude_ / 360.0; | ||||
|   return base + add; | ||||
| } | ||||
| uint32_t Sun::calc_epoch_(time::ESPTime base, double sun_time) { | ||||
|   sun_time -= this->longitude_ / 360.0; | ||||
|   base.day_of_year = uint32_t(floor(sun_time)); | ||||
|  | ||||
|   sun_time = (sun_time - base.day_of_year) * 24.0; | ||||
|   base.hour = uint32_t(floor(sun_time)); | ||||
|  | ||||
|   sun_time = (sun_time - base.hour) * 60.0; | ||||
|   base.minute = uint32_t(floor(sun_time)); | ||||
|  | ||||
|   sun_time = (sun_time - base.minute) * 60.0; | ||||
|   base.second = uint32_t(floor(sun_time)); | ||||
|  | ||||
|   base.recalc_timestamp_utc(true); | ||||
|   return base.timestamp; | ||||
| } | ||||
| double Sun::sun_time_for_elevation_(int32_t day_of_year, double elevation, bool rising) { | ||||
|   // Use binary search, newton's method would be better but binary search already | ||||
|   // converges quite well (19 cycles) and much simpler. Function is guaranteed to be | ||||
|   // monotonous. | ||||
|   double lo, hi; | ||||
|   if (rising) { | ||||
|     lo = day_of_year + 0.0; | ||||
|     hi = day_of_year + 0.5; | ||||
|   } else { | ||||
|     lo = day_of_year + 1.0; | ||||
|     hi = day_of_year + 0.5; | ||||
|   } | ||||
|  | ||||
|   double min_elevation = this->elevation_(lo); | ||||
|   double max_elevation = this->elevation_(hi); | ||||
|   if (elevation < min_elevation || elevation > max_elevation) | ||||
|     return NAN; | ||||
|  | ||||
|   // Accuracy: 0.1s | ||||
|   const double accuracy = 1.0 / (24.0 * 60.0 * 60.0 * 10.0); | ||||
|  | ||||
|   while (fabs(hi - lo) > accuracy) { | ||||
|     double mid = (lo + hi) / 2.0; | ||||
|     double value = this->elevation_(mid) - elevation; | ||||
|     if (value < 0) { | ||||
|       lo = mid; | ||||
|     } else if (value > 0) { | ||||
|       hi = mid; | ||||
|     } else { | ||||
|       lo = hi = mid; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return (lo + hi) / 2.0; | ||||
| } | ||||
|  | ||||
| }  // namespace sun | ||||
| }  // namespace esphome | ||||
							
								
								
									
										137
									
								
								esphome/components/sun/sun.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								esphome/components/sun/sun.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/components/time/real_time_clock.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sun { | ||||
|  | ||||
| class Sun { | ||||
|  public: | ||||
|   void set_time(time::RealTimeClock *time) { time_ = time; } | ||||
|   time::RealTimeClock *get_time() const { return time_; } | ||||
|   void set_latitude(double latitude) { latitude_ = latitude; } | ||||
|   void set_longitude(double longitude) { longitude_ = longitude; } | ||||
|  | ||||
|   optional<time::ESPTime> sunrise(double elevation = 0.0); | ||||
|   optional<time::ESPTime> sunset(double elevation = 0.0); | ||||
|  | ||||
|   double elevation(); | ||||
|   double azimuth(); | ||||
|  | ||||
|  protected: | ||||
|   double current_sun_time_() { return this->calc_sun_time_(this->time_->utcnow()); } | ||||
|  | ||||
|   /** Calculate the declination of the sun in rad. | ||||
|    * | ||||
|    * See https://en.wikipedia.org/wiki/Position_of_the_Sun#Declination_of_the_Sun_as_seen_from_Earth | ||||
|    * | ||||
|    * Accuracy: ±0.2° | ||||
|    * | ||||
|    * @param sun_time The day of the year, 1 means January 1st. See calc_sun_time_. | ||||
|    * @return Sun declination in degrees | ||||
|    */ | ||||
|   double sun_declination_(double sun_time); | ||||
|  | ||||
|   double elevation_ratio_(double sun_time); | ||||
|  | ||||
|   /** Calculate the hour angle based on the sun time of day in hours. | ||||
|    * | ||||
|    * Positive in morning, 0 at noon, negative in afternoon. | ||||
|    * | ||||
|    * @param sun_time Sun time, see calc_sun_time_. | ||||
|    * @return Hour angle in rad. | ||||
|    */ | ||||
|   double hour_angle_(double sun_time); | ||||
|  | ||||
|   double elevation_(double sun_time); | ||||
|  | ||||
|   double elevation_rad_(double sun_time); | ||||
|  | ||||
|   double zenith_rad_(double sun_time); | ||||
|  | ||||
|   double azimuth_rad_(double sun_time); | ||||
|  | ||||
|   double azimuth_(double sun_time); | ||||
|  | ||||
|   /** Return the sun time given by the time_ object. | ||||
|    * | ||||
|    * Sun time is defined as doubleing point day of year. | ||||
|    * Integer part encodes the day of the year (1=January 1st) | ||||
|    * Decimal part encodes time of day (1/24 = 1 hour) | ||||
|    */ | ||||
|   double calc_sun_time_(const time::ESPTime &time); | ||||
|  | ||||
|   uint32_t calc_epoch_(time::ESPTime base, double sun_time); | ||||
|  | ||||
|   /** Calculate the sun time of day | ||||
|    * | ||||
|    * @param day_of_year | ||||
|    * @param elevation | ||||
|    * @param rising | ||||
|    * @return | ||||
|    */ | ||||
|   double sun_time_for_elevation_(int32_t day_of_year, double elevation, bool rising); | ||||
|  | ||||
|   double latitude_rad_(); | ||||
|  | ||||
|   time::RealTimeClock *time_; | ||||
|   /// Latitude in degrees, range: -90 to 90. | ||||
|   double latitude_; | ||||
|   /// Longitude in degrees, range: -180 to 180. | ||||
|   double longitude_; | ||||
| }; | ||||
|  | ||||
| class SunTrigger : public Trigger<>, public PollingComponent, public Parented<Sun> { | ||||
|  public: | ||||
|   SunTrigger() : PollingComponent(1000) {} | ||||
|  | ||||
|   void set_sunrise(bool sunrise) { sunrise_ = sunrise; } | ||||
|   void set_elevation(double elevation) { elevation_ = elevation; } | ||||
|  | ||||
|   void update() override { | ||||
|     double current = this->parent_->elevation(); | ||||
|     if (isnan(current)) | ||||
|       return; | ||||
|  | ||||
|     bool crossed; | ||||
|     if (this->sunrise_) { | ||||
|       crossed = this->last_elevation_ <= this->elevation_ && this->elevation_ < current; | ||||
|     } else { | ||||
|       crossed = this->last_elevation_ >= this->elevation_ && this->elevation_ > current; | ||||
|     } | ||||
|  | ||||
|     if (crossed) { | ||||
|       this->trigger(); | ||||
|     } | ||||
|     this->last_elevation_ = current; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   bool sunrise_; | ||||
|   double last_elevation_; | ||||
|   double elevation_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class SunCondition : public Condition<Ts...>, public Parented<Sun> { | ||||
|  public: | ||||
|   TEMPLATABLE_VALUE(double, elevation); | ||||
|   void set_above(bool above) { above_ = above; } | ||||
|  | ||||
|   bool check(Ts... x) override { | ||||
|     double elevation = this->elevation_.value(x...); | ||||
|     double current = this->parent_->elevation(); | ||||
|     if (this->above_) | ||||
|       return current > elevation; | ||||
|     else | ||||
|       return current < elevation; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   bool above_; | ||||
| }; | ||||
|  | ||||
| }  // namespace sun | ||||
| }  // namespace esphome | ||||
							
								
								
									
										45
									
								
								esphome/components/sun/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/sun/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| from esphome.components import text_sensor | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
| from esphome.const import CONF_ICON, ICON_WEATHER_SUNSET_DOWN, ICON_WEATHER_SUNSET_UP, CONF_TYPE, \ | ||||
|     CONF_ID, CONF_FORMAT | ||||
| from .. import sun_ns, CONF_SUN_ID, Sun, CONF_ELEVATION, elevation | ||||
|  | ||||
| DEPENDENCIES = ['sun'] | ||||
|  | ||||
| SunTextSensor = sun_ns.class_('SunTextSensor', text_sensor.TextSensor, cg.PollingComponent) | ||||
| SUN_TYPES = { | ||||
|     'sunset': False, | ||||
|     'sunrise': True, | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_optional_icon(config): | ||||
|     if CONF_ICON not in config: | ||||
|         config = config.copy() | ||||
|         config[CONF_ICON] = { | ||||
|             'sunset': ICON_WEATHER_SUNSET_DOWN, | ||||
|             'sunrise': ICON_WEATHER_SUNSET_UP, | ||||
|         }[config[CONF_TYPE]] | ||||
|     return config | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(SunTextSensor), | ||||
|     cv.GenerateID(CONF_SUN_ID): cv.use_id(Sun), | ||||
|     cv.Required(CONF_TYPE): cv.one_of(*SUN_TYPES, lower=True), | ||||
|     cv.Optional(CONF_ELEVATION, default=0): elevation, | ||||
|     cv.Optional(CONF_FORMAT, default='%X'): cv.string_strict, | ||||
| }).extend(cv.polling_component_schema('60s')), validate_optional_icon) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield text_sensor.register_text_sensor(var, config) | ||||
|  | ||||
|     paren = yield cg.get_variable(config[CONF_SUN_ID]) | ||||
|     cg.add(var.set_parent(paren)) | ||||
|     cg.add(var.set_sunrise(SUN_TYPES[config[CONF_TYPE]])) | ||||
|     cg.add(var.set_elevation(config[CONF_ELEVATION])) | ||||
|     cg.add(var.set_format(config[CONF_FORMAT])) | ||||
							
								
								
									
										12
									
								
								esphome/components/sun/text_sensor/sun_text_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/sun/text_sensor/sun_text_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #include "sun_text_sensor.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sun { | ||||
|  | ||||
| static const char *TAG = "sun.text_sensor"; | ||||
|  | ||||
| void SunTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Sun Text Sensor", this); } | ||||
|  | ||||
| }  // namespace sun | ||||
| }  // namespace esphome | ||||
							
								
								
									
										41
									
								
								esphome/components/sun/text_sensor/sun_text_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/sun/text_sensor/sun_text_sensor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sun/sun.h" | ||||
| #include "esphome/components/text_sensor/text_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sun { | ||||
|  | ||||
| class SunTextSensor : public text_sensor::TextSensor, public PollingComponent { | ||||
|  public: | ||||
|   void set_parent(Sun *parent) { parent_ = parent; } | ||||
|   void set_elevation(double elevation) { elevation_ = elevation; } | ||||
|   void set_sunrise(bool sunrise) { sunrise_ = sunrise; } | ||||
|   void set_format(const std::string &format) { format_ = format; } | ||||
|  | ||||
|   void update() override { | ||||
|     optional<time::ESPTime> res; | ||||
|     if (this->sunrise_) | ||||
|       res = this->parent_->sunrise(this->elevation_); | ||||
|     else | ||||
|       res = this->parent_->sunset(this->elevation_); | ||||
|     if (!res) { | ||||
|       this->publish_state(""); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this->publish_state(res->strftime(this->format_)); | ||||
|   } | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   std::string format_{}; | ||||
|   Sun *parent_; | ||||
|   double elevation_; | ||||
|   bool sunrise_; | ||||
| }; | ||||
|  | ||||
| }  // namespace sun | ||||
| }  // namespace esphome | ||||
| @@ -10,7 +10,7 @@ TemplateBinarySensor = template_ns.class_('TemplateBinarySensor', binary_sensor. | ||||
|  | ||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(TemplateBinarySensor), | ||||
|     cv.Optional(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Optional(CONF_LAMBDA): cv.returning_lambda, | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -18,7 +18,7 @@ RESTORE_MODES = { | ||||
|  | ||||
| CONFIG_SCHEMA = cover.COVER_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(TemplateCover), | ||||
|     cv.Optional(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Optional(CONF_LAMBDA): cv.returning_lambda, | ||||
|     cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, | ||||
|     cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, | ||||
|     cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True), | ||||
|   | ||||
							
								
								
									
										35
									
								
								esphome/components/template/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/template/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.components import output | ||||
| from esphome.const import CONF_ID, CONF_TYPE | ||||
| from .. import template_ns | ||||
|  | ||||
| TemplateBinaryOutput = template_ns.class_('TemplateBinaryOutput', output.BinaryOutput) | ||||
| TemplateFloatOutput = template_ns.class_('TemplateFloatOutput', output.FloatOutput) | ||||
|  | ||||
| CONF_BINARY = 'binary' | ||||
| CONF_FLOAT = 'float' | ||||
| CONF_WRITE_ACTION = 'write_action' | ||||
|  | ||||
| CONFIG_SCHEMA = cv.typed_schema({ | ||||
|     CONF_BINARY: output.BINARY_OUTPUT_SCHEMA.extend({ | ||||
|         cv.GenerateID(): cv.declare_id(TemplateBinaryOutput), | ||||
|         cv.Required(CONF_WRITE_ACTION): automation.validate_automation(single=True), | ||||
|     }), | ||||
|     CONF_FLOAT: output.FLOAT_OUTPUT_SCHEMA.extend({ | ||||
|         cv.GenerateID(): cv.declare_id(TemplateFloatOutput), | ||||
|         cv.Required(CONF_WRITE_ACTION): automation.validate_automation(single=True), | ||||
|     }), | ||||
| }, lower=True) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     if config[CONF_TYPE] == CONF_BINARY: | ||||
|         yield automation.build_automation(var.get_trigger(), [(bool, 'state')], | ||||
|                                           config[CONF_WRITE_ACTION]) | ||||
|     else: | ||||
|         yield automation.build_automation(var.get_trigger(), [(float, 'state')], | ||||
|                                           config[CONF_WRITE_ACTION]) | ||||
|     yield output.register_output(var, config) | ||||
							
								
								
									
										31
									
								
								esphome/components/template/output/template_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/template/output/template_output.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/components/output/binary_output.h" | ||||
| #include "esphome/components/output/float_output.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace template_ { | ||||
|  | ||||
| class TemplateBinaryOutput : public output::BinaryOutput { | ||||
|  public: | ||||
|   Trigger<bool> *get_trigger() const { return trigger_; } | ||||
|  | ||||
|  protected: | ||||
|   void write_state(bool state) override { this->trigger_->trigger(state); } | ||||
|  | ||||
|   Trigger<bool> *trigger_ = new Trigger<bool>(); | ||||
| }; | ||||
|  | ||||
| class TemplateFloatOutput : public output::FloatOutput { | ||||
|  public: | ||||
|   Trigger<float> *get_trigger() const { return trigger_; } | ||||
|  | ||||
|  protected: | ||||
|   void write_state(float state) override { this->trigger_->trigger(state); } | ||||
|  | ||||
|   Trigger<float> *trigger_ = new Trigger<float>(); | ||||
| }; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
| @@ -9,7 +9,7 @@ TemplateSensor = template_ns.class_('TemplateSensor', sensor.Sensor, cg.PollingC | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1).extend({ | ||||
|     cv.GenerateID(): cv.declare_id(TemplateSensor), | ||||
|     cv.Optional(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Optional(CONF_LAMBDA): cv.returning_lambda, | ||||
| }).extend(cv.polling_component_schema('60s')) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ TemplateSwitch = template_ns.class_('TemplateSwitch', switch.Switch, cg.Componen | ||||
|  | ||||
| CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(TemplateSwitch), | ||||
|     cv.Optional(CONF_LAMBDA): cv.lambda_, | ||||
|     cv.Optional(CONF_LAMBDA): cv.returning_lambda, | ||||
|     cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, | ||||
|     cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, | ||||
|     cv.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation(single=True), | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user