diff --git a/esphome/components/sensor/sds011.py b/esphome/components/sensor/sds011.py new file mode 100644 index 0000000000..317f7143bd --- /dev/null +++ b/esphome/components/sensor/sds011.py @@ -0,0 +1,78 @@ +import voluptuous as vol + +from esphome.components import sensor, uart +from esphome.components.uart import UARTComponent +import esphome.config_validation as cv +from esphome.const import (CONF_ID, CONF_NAME, CONF_PM_10_0, CONF_PM_2_5, CONF_RX_ONLY, + CONF_UART_ID, CONF_UPDATE_INTERVAL) +from esphome.cpp_generator import Pvariable, add, get_variable +from esphome.cpp_helpers import setup_component +from esphome.cpp_types import App, Component + +DEPENDENCIES = ['uart'] + +SDS011Component = sensor.sensor_ns.class_('SDS011Component', uart.UARTDevice, Component) +SDS011Sensor = sensor.sensor_ns.class_('SDS011Sensor', sensor.EmptySensor) + + +def validate_sds011_rx_mode(value): + if CONF_UPDATE_INTERVAL in value and not value.get(CONF_RX_ONLY): + update_interval = value[CONF_UPDATE_INTERVAL] + if update_interval.total_minutes > 30: + raise vol.Invalid("Maximum update interval is 30min") + elif value.get(CONF_RX_ONLY) and CONF_UPDATE_INTERVAL in value: + # update_interval does not affect anything in rx-only mode, let's warn user about + # that + raise vol.Invalid("update_interval has no effect in rx_only mode. Please remove it.", + path=['update_interval']) + return value + + +SDS011_SENSOR_SCHEMA = sensor.SENSOR_SCHEMA.extend({ + cv.GenerateID(): cv.declare_variable_id(SDS011Sensor), +}) + +PLATFORM_SCHEMA = vol.All(sensor.PLATFORM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_variable_id(SDS011Component), + cv.GenerateID(CONF_UART_ID): cv.use_variable_id(UARTComponent), + + vol.Optional(CONF_RX_ONLY): cv.boolean, + + vol.Optional(CONF_PM_2_5): cv.nameable(SDS011_SENSOR_SCHEMA), + vol.Optional(CONF_PM_10_0): cv.nameable(SDS011_SENSOR_SCHEMA), + vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_minutes, +}).extend(cv.COMPONENT_SCHEMA.schema), cv.has_at_least_one_key(CONF_PM_2_5, CONF_PM_10_0), + validate_sds011_rx_mode) + + +def to_code(config): + for uart_ in get_variable(config[CONF_UART_ID]): + yield + + rhs = App.make_sds011(uart_) + sds011 = Pvariable(config[CONF_ID], rhs) + + if CONF_UPDATE_INTERVAL in config: + add(sds011.set_update_interval_min(config.get(CONF_UPDATE_INTERVAL))) + if CONF_RX_ONLY in config: + add(sds011.set_rx_mode_only(config[CONF_RX_ONLY])) + + if CONF_PM_2_5 in config: + conf = config[CONF_PM_2_5] + sensor.register_sensor(sds011.make_pm_2_5_sensor(conf[CONF_NAME]), conf) + if CONF_PM_10_0 in config: + conf = config[CONF_PM_10_0] + sensor.register_sensor(sds011.make_pm_10_0_sensor(conf[CONF_NAME]), conf) + + setup_component(sds011, config) + + +BUILD_FLAGS = '-DUSE_SDS011' + + +def to_hass_config(data, config): + ret = [] + for key in (CONF_PM_2_5, CONF_PM_10_0): + if key in config: + ret.append(sensor.core_to_hass_config(data, config[key])) + return ret diff --git a/esphome/config_validation.py b/esphome/config_validation.py index e82268ae63..ed9ece9a20 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -15,7 +15,7 @@ from esphome.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY, CONF_RETAIN, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_TOPIC, ESP_PLATFORM_ESP32, \ ESP_PLATFORM_ESP8266 from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \ - TimePeriodMilliseconds, TimePeriodSeconds + TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes from esphome.py_compat import integer_types, string_types, text_type from esphome.voluptuous_schema import _Schema @@ -363,6 +363,16 @@ def time_period_in_seconds_(value): return TimePeriodSeconds(**value.as_dict()) +def time_period_in_minutes_(value): + if value.microseconds is not None and value.microseconds != 0: + raise vol.Invalid("Maximum precision is minutes") + if value.milliseconds is not None and value.milliseconds != 0: + raise vol.Invalid("Maximum precision is minutes") + if value.seconds is not None and value.seconds != 0: + raise vol.Invalid("Maximum precision is minutes") + return TimePeriodMinutes(**value.as_dict()) + + def update_interval(value): if value == 'never': return 4294967295 # uint32_t max @@ -373,6 +383,7 @@ time_period = vol.Any(time_period_str_unit, time_period_str_colon, time_period_d positive_time_period = vol.All(time_period, vol.Range(min=TimePeriod())) positive_time_period_milliseconds = vol.All(positive_time_period, time_period_in_milliseconds_) positive_time_period_seconds = vol.All(positive_time_period, time_period_in_seconds_) +positive_time_period_minutes = vol.All(positive_time_period, time_period_in_minutes_) time_period_microseconds = vol.All(time_period, time_period_in_microseconds_) positive_time_period_microseconds = vol.All(positive_time_period, time_period_in_microseconds_) positive_not_null_time_period = vol.All(time_period, diff --git a/esphome/const.py b/esphome/const.py index ff816bcc6d..1d08291fac 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -420,6 +420,7 @@ CONF_SEGMENTS = 'segments' CONF_MIN_POWER = 'min_power' CONF_MIN_VALUE = 'min_value' CONF_MAX_VALUE = 'max_value' +CONF_RX_ONLY = 'rx_only' ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_' diff --git a/esphome/core.py b/esphome/core.py index 92e75f9c40..0b89e82ec0 100644 --- a/esphome/core.py +++ b/esphome/core.py @@ -215,6 +215,10 @@ class TimePeriodSeconds(TimePeriod): pass +class TimePeriodMinutes(TimePeriod): + pass + + LAMBDA_PROG = re.compile(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)') diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index f084be376a..5b6f004bb7 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -1,7 +1,7 @@ from collections import OrderedDict from esphome.core import CORE, HexInt, Lambda, TimePeriod, TimePeriodMicroseconds, \ - TimePeriodMilliseconds, TimePeriodSeconds + TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last # pylint: disable=unused-import, wrong-import-order @@ -286,6 +286,8 @@ def safe_exp( return IntLiteral(int(obj.total_milliseconds)) if isinstance(obj, TimePeriodSeconds): return IntLiteral(int(obj.total_seconds)) + if isinstance(obj, TimePeriodMinutes): + return IntLiteral(int(obj.total_minutes)) if isinstance(obj, (tuple, list)): return ArrayInitializer(*[safe_exp(o) for o in obj]) raise ValueError(u"Object is not an expression", obj) diff --git a/tests/test1.yaml b/tests/test1.yaml index b5877d2c36..4cd77c1b07 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -493,6 +493,13 @@ sensor: payload: |- root["key"] = id(the_sensor).state; root["greeting"] = "Hello World"; + - platform: sds011 + pm_2_5: + name: "SDS011 PM2.5" + pm_10_0: + name: "SDS011 PM10.0" + update_interval: 5min + rx_only: false esp32_touch: