From 19929fafa562bd5eca84a56394413022502664b0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 17 May 2018 19:57:55 +0200 Subject: [PATCH 1/2] Rotary Encoder --- esphomeyaml/components/binary_sensor/gpio.py | 5 +- esphomeyaml/components/deep_sleep.py | 14 ++--- esphomeyaml/components/ir_transmitter.py | 4 +- esphomeyaml/components/output/esp8266_pwm.py | 11 ++-- esphomeyaml/components/output/gpio.py | 4 +- esphomeyaml/components/power_supply.py | 4 +- esphomeyaml/components/sensor/dht.py | 4 +- .../components/sensor/rotary_encoder.py | 44 +++++++++++++++ esphomeyaml/components/sensor/ultrasonic.py | 8 +-- esphomeyaml/components/switch/gpio.py | 4 +- esphomeyaml/helpers.py | 53 +++++++++---------- esphomeyaml/pins.py | 48 ++++++++++------- 12 files changed, 126 insertions(+), 77 deletions(-) create mode 100644 esphomeyaml/components/sensor/rotary_encoder.py diff --git a/esphomeyaml/components/binary_sensor/gpio.py b/esphomeyaml/components/binary_sensor/gpio.py index c4e362aa13..04e5b9a6f3 100644 --- a/esphomeyaml/components/binary_sensor/gpio.py +++ b/esphomeyaml/components/binary_sensor/gpio.py @@ -4,7 +4,7 @@ import esphomeyaml.config_validation as cv from esphomeyaml import pins from esphomeyaml.components import binary_sensor from esphomeyaml.const import CONF_ID, CONF_INVERTED, CONF_NAME, CONF_PIN -from esphomeyaml.helpers import App, add, exp_gpio_input_pin, variable +from esphomeyaml.helpers import App, add, variable, gpio_input_pin_expression PLATFORM_SCHEMA = binary_sensor.PLATFORM_SCHEMA.extend({ cv.GenerateID('gpio_binary_sensor'): cv.register_variable_id, @@ -13,7 +13,8 @@ PLATFORM_SCHEMA = binary_sensor.PLATFORM_SCHEMA.extend({ def to_code(config): - rhs = App.make_gpio_binary_sensor(config[CONF_NAME], exp_gpio_input_pin(config[CONF_PIN])) + rhs = App.make_gpio_binary_sensor(config[CONF_NAME], + gpio_input_pin_expression(config[CONF_PIN])) gpio = variable('Application::MakeGPIOBinarySensor', config[CONF_ID], rhs) if CONF_INVERTED in config: add(gpio.Pgpio.set_inverted(config[CONF_INVERTED])) diff --git a/esphomeyaml/components/deep_sleep.py b/esphomeyaml/components/deep_sleep.py index 2ddf2a8cd3..1818897e4e 100644 --- a/esphomeyaml/components/deep_sleep.py +++ b/esphomeyaml/components/deep_sleep.py @@ -1,14 +1,14 @@ import voluptuous as vol from esphomeyaml import config_validation as cv, pins -from esphomeyaml.const import CONF_ID, CONF_RUN_CYCLES, CONF_RUN_DURATION, CONF_SLEEP_DURATION, \ - CONF_WAKEUP_PIN -from esphomeyaml.helpers import App, Pvariable, add, exp_gpio_input_pin +from esphomeyaml.const import CONF_ID, CONF_NUMBER, CONF_RUN_CYCLES, CONF_RUN_DURATION, \ + CONF_SLEEP_DURATION, CONF_WAKEUP_PIN +from esphomeyaml.helpers import App, Pvariable, add, gpio_input_pin_expression def validate_pin_number(value): valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 39] - if value not in valid_pins: + if value[CONF_NUMBER] not in valid_pins: raise vol.Invalid(u"Only pins {} support wakeup" u"".format(', '.join(str(x) for x in valid_pins))) return value @@ -17,8 +17,8 @@ def validate_pin_number(value): CONFIG_SCHEMA = vol.Schema({ cv.GenerateID('deep_sleep'): cv.register_variable_id, vol.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, - vol.Optional(CONF_WAKEUP_PIN): vol.All(cv.only_on_esp32, pins.GPIO_INPUT_PIN_SCHEMA, - pins.schema_validate_number(validate_pin_number)), + vol.Optional(CONF_WAKEUP_PIN): vol.All(cv.only_on_esp32, pins.GPIO_INTERNAL_INPUT_PIN_SCHEMA, + validate_pin_number), vol.Optional(CONF_RUN_CYCLES): cv.positive_int, vol.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds, }) @@ -30,7 +30,7 @@ def to_code(config): if CONF_SLEEP_DURATION in config: add(deep_sleep.set_sleep_duration(config[CONF_SLEEP_DURATION])) if CONF_WAKEUP_PIN in config: - pin = exp_gpio_input_pin(config[CONF_WAKEUP_PIN]) + pin = gpio_input_pin_expression(config[CONF_WAKEUP_PIN]) add(deep_sleep.set_wakeup_pin(pin)) if CONF_RUN_CYCLES in config: add(deep_sleep.set_run_cycles(config[CONF_RUN_CYCLES])) diff --git a/esphomeyaml/components/ir_transmitter.py b/esphomeyaml/components/ir_transmitter.py index c6595636ba..c402aedeb9 100644 --- a/esphomeyaml/components/ir_transmitter.py +++ b/esphomeyaml/components/ir_transmitter.py @@ -3,7 +3,7 @@ import voluptuous as vol import esphomeyaml.config_validation as cv from esphomeyaml import pins from esphomeyaml.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN -from esphomeyaml.helpers import App, Pvariable, exp_gpio_output_pin +from esphomeyaml.helpers import App, Pvariable, gpio_output_pin_expression IR_TRANSMITTER_COMPONENT_CLASS = 'switch_::IRTransmitterComponent' @@ -17,7 +17,7 @@ CONFIG_SCHEMA = vol.All(cv.ensure_list, [vol.Schema({ def to_code(config): for conf in config: - pin = exp_gpio_output_pin(conf[CONF_PIN]) + pin = gpio_output_pin_expression(conf[CONF_PIN]) rhs = App.make_ir_transmitter(pin, conf.get(CONF_CARRIER_DUTY_PERCENT)) Pvariable(IR_TRANSMITTER_COMPONENT_CLASS, conf[CONF_ID], rhs) diff --git a/esphomeyaml/components/output/esp8266_pwm.py b/esphomeyaml/components/output/esp8266_pwm.py index c89db95604..8bc507df2c 100644 --- a/esphomeyaml/components/output/esp8266_pwm.py +++ b/esphomeyaml/components/output/esp8266_pwm.py @@ -2,27 +2,26 @@ import voluptuous as vol from esphomeyaml import pins from esphomeyaml.components import output -from esphomeyaml.const import CONF_ID, CONF_PIN, ESP_PLATFORM_ESP8266 +from esphomeyaml.const import CONF_ID, CONF_PIN, ESP_PLATFORM_ESP8266, CONF_NUMBER from esphomeyaml.core import ESPHomeYAMLError -from esphomeyaml.helpers import App, Pvariable, exp_gpio_output_pin +from esphomeyaml.helpers import App, Pvariable, gpio_output_pin_expression ESP_PLATFORMS = [ESP_PLATFORM_ESP8266] def valid_pwm_pin(value): - if value >= 16: + if value[CONF_NUMBER] >= 16: raise ESPHomeYAMLError(u"ESP8266: Only pins 0-16 support PWM.") return value PLATFORM_SCHEMA = output.FLOAT_PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PIN): vol.All(pins.GPIO_OUTPUT_PIN_SCHEMA, - pins.schema_validate_number(valid_pwm_pin)), + vol.Required(CONF_PIN): vol.All(pins.GPIO_INTERNAL_OUTPUT_PIN_SCHEMA, valid_pwm_pin), }) def to_code(config): - pin = exp_gpio_output_pin(config[CONF_PIN]) + pin = gpio_output_pin_expression(config[CONF_PIN]) rhs = App.make_esp8266_pwm_output(pin) gpio = Pvariable('output::ESP8266PWMOutput', config[CONF_ID], rhs) output.setup_output_platform(gpio, config) diff --git a/esphomeyaml/components/output/gpio.py b/esphomeyaml/components/output/gpio.py index 0a4d11248d..c6c67a396f 100644 --- a/esphomeyaml/components/output/gpio.py +++ b/esphomeyaml/components/output/gpio.py @@ -3,7 +3,7 @@ import voluptuous as vol from esphomeyaml import pins from esphomeyaml.components import output from esphomeyaml.const import CONF_ID, CONF_PIN -from esphomeyaml.helpers import App, Pvariable, exp_gpio_output_pin +from esphomeyaml.helpers import App, Pvariable, gpio_output_pin_expression PLATFORM_SCHEMA = output.PLATFORM_SCHEMA.extend({ vol.Required(CONF_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, @@ -11,7 +11,7 @@ PLATFORM_SCHEMA = output.PLATFORM_SCHEMA.extend({ def to_code(config): - pin = exp_gpio_output_pin(config[CONF_PIN]) + pin = gpio_output_pin_expression(config[CONF_PIN]) rhs = App.make_gpio_output(pin) gpio = Pvariable('output::GPIOBinaryOutputComponent', config[CONF_ID], rhs) output.setup_output_platform(gpio, config) diff --git a/esphomeyaml/components/power_supply.py b/esphomeyaml/components/power_supply.py index c9b240a863..d70383454d 100644 --- a/esphomeyaml/components/power_supply.py +++ b/esphomeyaml/components/power_supply.py @@ -3,7 +3,7 @@ import voluptuous as vol import esphomeyaml.config_validation as cv from esphomeyaml import pins from esphomeyaml.const import CONF_ENABLE_TIME, CONF_ID, CONF_KEEP_ON_TIME, CONF_PIN -from esphomeyaml.helpers import App, Pvariable, add, exp_gpio_output_pin +from esphomeyaml.helpers import App, Pvariable, add, gpio_output_pin_expression POWER_SUPPLY_SCHEMA = cv.REQUIRED_ID_SCHEMA.extend({ vol.Required(CONF_PIN): pins.GPIO_OUTPUT_PIN_SCHEMA, @@ -16,7 +16,7 @@ CONFIG_SCHEMA = vol.All(cv.ensure_list, [POWER_SUPPLY_SCHEMA]) def to_code(config): for conf in config: - pin = exp_gpio_output_pin(conf[CONF_PIN]) + pin = gpio_output_pin_expression(conf[CONF_PIN]) rhs = App.make_power_supply(pin) psu = Pvariable('PowerSupplyComponent', conf[CONF_ID], rhs) if CONF_ENABLE_TIME in conf: diff --git a/esphomeyaml/components/sensor/dht.py b/esphomeyaml/components/sensor/dht.py index 0aef34ac7e..54edae5e72 100644 --- a/esphomeyaml/components/sensor/dht.py +++ b/esphomeyaml/components/sensor/dht.py @@ -5,7 +5,7 @@ from esphomeyaml.components import sensor from esphomeyaml.components.sensor import MQTT_SENSOR_SCHEMA from esphomeyaml.const import CONF_HUMIDITY, CONF_ID, CONF_MODEL, CONF_NAME, CONF_PIN, \ CONF_TEMPERATURE, CONF_UPDATE_INTERVAL -from esphomeyaml.helpers import App, RawExpression, add, variable, exp_gpio_output_pin +from esphomeyaml.helpers import App, RawExpression, add, variable, gpio_output_pin_expression from esphomeyaml.pins import GPIO_OUTPUT_PIN_SCHEMA DHT_MODELS = { @@ -27,7 +27,7 @@ PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ def to_code(config): - pin = exp_gpio_output_pin(config[CONF_PIN]) + pin = gpio_output_pin_expression(config[CONF_PIN]) rhs = App.make_dht_sensor(config[CONF_TEMPERATURE][CONF_NAME], config[CONF_HUMIDITY][CONF_NAME], pin, config.get(CONF_UPDATE_INTERVAL)) diff --git a/esphomeyaml/components/sensor/rotary_encoder.py b/esphomeyaml/components/sensor/rotary_encoder.py new file mode 100644 index 0000000000..c470da5cdd --- /dev/null +++ b/esphomeyaml/components/sensor/rotary_encoder.py @@ -0,0 +1,44 @@ +import voluptuous as vol + +import esphomeyaml.config_validation as cv +from esphomeyaml import pins +from esphomeyaml.components import sensor +from esphomeyaml.const import CONF_ID, CONF_NAME, CONF_RESOLUTION +from esphomeyaml.helpers import App, RawExpression, add, gpio_input_pin_expression, variable + +RESOLUTIONS = { + '1': 'sensor::ROTARY_ENCODER_1_PULSE_PER_CYCLE', + '2': 'sensor::ROTARY_ENCODER_2_PULSES_PER_CYCLE', + '4': 'sensor::ROTARY_ENCODER_4_PULSES_PER_CYCLE', +} + +CONF_PIN_A = 'pin_a' +CONF_PIN_B = 'pin_b' +CONF_PIN_RESET = 'pin_reset' + +PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ + cv.GenerateID('rotary_encoder'): cv.register_variable_id, + vol.Required(CONF_PIN_A): pins.GPIO_INTERNAL_INPUT_PIN_SCHEMA, + vol.Required(CONF_PIN_B): pins.GPIO_INTERNAL_INPUT_PIN_SCHEMA, + vol.Optional(CONF_PIN_RESET): pins.GPIO_INTERNAL_INPUT_PIN_SCHEMA, + vol.Optional(CONF_RESOLUTION): vol.All(cv.string, vol.Any(*RESOLUTIONS)), +}).extend(sensor.MQTT_SENSOR_SCHEMA.schema) + + +def to_code(config): + pin_a = gpio_input_pin_expression(config[CONF_PIN_A]) + pin_b = gpio_input_pin_expression(config[CONF_PIN_B]) + rhs = App.make_rotary_encoder_sensor(config[CONF_NAME], pin_a, pin_b) + make = variable('Application::MakeRotaryEncoderSensor', config[CONF_ID], rhs) + encoder = make.Protary_encoder + if CONF_PIN_RESET in config: + pin_i = gpio_input_pin_expression(config[CONF_PIN_RESET]) + add(encoder.set_reset_pin(pin_i)) + if CONF_RESOLUTION in config: + resolution = RESOLUTIONS[config[CONF_RESOLUTION]] + add(encoder.set_resolution(RawExpression(resolution))) + sensor.setup_sensor(encoder, config) + sensor.setup_mqtt_sensor_component(make.Pmqtt, config) + + +BUILD_FLAGS = '-DUSE_ROTARY_ENCODER_SENSOR' diff --git a/esphomeyaml/components/sensor/ultrasonic.py b/esphomeyaml/components/sensor/ultrasonic.py index 557e2989bd..1144bf8421 100644 --- a/esphomeyaml/components/sensor/ultrasonic.py +++ b/esphomeyaml/components/sensor/ultrasonic.py @@ -5,8 +5,8 @@ from esphomeyaml import pins from esphomeyaml.components import sensor from esphomeyaml.const import CONF_ECHO_PIN, CONF_ID, CONF_NAME, \ CONF_TIMEOUT_METER, CONF_TIMEOUT_TIME, CONF_TRIGGER_PIN, CONF_UPDATE_INTERVAL -from esphomeyaml.helpers import App, add, exp_gpio_input_pin, exp_gpio_output_pin, \ - variable +from esphomeyaml.helpers import App, add, variable, gpio_output_pin_expression, \ + gpio_input_pin_expression PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ cv.GenerateID('ultrasonic'): cv.register_variable_id, @@ -19,8 +19,8 @@ PLATFORM_SCHEMA = sensor.PLATFORM_SCHEMA.extend({ def to_code(config): - trigger = exp_gpio_output_pin(config[CONF_TRIGGER_PIN]) - echo = exp_gpio_input_pin(config[CONF_ECHO_PIN]) + trigger = gpio_output_pin_expression(config[CONF_TRIGGER_PIN]) + echo = gpio_input_pin_expression(config[CONF_ECHO_PIN]) rhs = App.make_ultrasonic_sensor(config[CONF_NAME], trigger, echo, config.get(CONF_UPDATE_INTERVAL)) make = variable('Application::MakeUltrasonicSensor', config[CONF_ID], rhs) diff --git a/esphomeyaml/components/switch/gpio.py b/esphomeyaml/components/switch/gpio.py index e37872c6c8..cddd13b9ff 100644 --- a/esphomeyaml/components/switch/gpio.py +++ b/esphomeyaml/components/switch/gpio.py @@ -4,7 +4,7 @@ import esphomeyaml.config_validation as cv from esphomeyaml import pins from esphomeyaml.components import switch from esphomeyaml.const import CONF_ID, CONF_NAME, CONF_PIN -from esphomeyaml.helpers import App, exp_gpio_output_pin, variable +from esphomeyaml.helpers import App, variable, gpio_output_pin_expression PLATFORM_SCHEMA = switch.PLATFORM_SCHEMA.extend({ cv.GenerateID('gpio_switch'): cv.register_variable_id, @@ -13,7 +13,7 @@ PLATFORM_SCHEMA = switch.PLATFORM_SCHEMA.extend({ def to_code(config): - rhs = App.make_gpio_switch(config[CONF_NAME], exp_gpio_output_pin(config[CONF_PIN])) + rhs = App.make_gpio_switch(config[CONF_NAME], gpio_output_pin_expression(config[CONF_PIN])) gpio = variable('Application::MakeGPIOSwitch', config[CONF_ID], rhs) switch.setup_switch(gpio.Pswitch_, config) switch.setup_mqtt_switch(gpio.Pmqtt, config) diff --git a/esphomeyaml/helpers.py b/esphomeyaml/helpers.py index a891f5ad57..fa11b4073a 100644 --- a/esphomeyaml/helpers.py +++ b/esphomeyaml/helpers.py @@ -186,15 +186,15 @@ class Literal(Expression): # From https://stackoverflow.com/a/14945195/8924614 -def cpp_string_escape(s, encoding='utf-8'): - if isinstance(s, unicode): - s = s.encode(encoding) +def cpp_string_escape(string, encoding='utf-8'): + if isinstance(string, unicode): + string = string.encode(encoding) result = '' - for c in s: - if not (32 <= ord(c) < 127) or c in ('\\', '"'): - result += '\\%03o' % ord(c) + for character in string: + if not (32 <= ord(character) < 127) or character in ('\\', '"'): + result += '\\%03o' % ord(character) else: - result += c + result += character return '"' + result + '"' @@ -408,37 +408,34 @@ def get_gpio_pin_number(conf): return conf[CONF_NUMBER] -def exp_gpio_pin_(obj, conf, default_mode): - if isinstance(conf, int): - return conf - +def generic_gpio_pin_expression_(conf, mock_obj, default_mode): + if conf is None: + return None + number = conf[CONF_NUMBER] + inverted = conf.get(CONF_INVERTED) if CONF_PCF8574 in conf: hub = get_variable(conf[CONF_PCF8574], 'io::PCF8574Component') if default_mode == u'INPUT': - return hub.make_input_pin(conf[CONF_NUMBER], - RawExpression('PCF8574_' + conf[CONF_MODE]), - conf[CONF_INVERTED]) + mode = conf.get(CONF_MODE, u'INPUT') + return hub.make_input_pin(number, + RawExpression('PCF8574_' + mode), + inverted) elif default_mode == u'OUTPUT': - return hub.make_output_pin(conf[CONF_NUMBER], conf[CONF_INVERTED]) + return hub.make_output_pin(number, inverted) else: raise ESPHomeYAMLError(u"Unknown default mode {}".format(default_mode)) - - if conf.get(CONF_INVERTED) is None: - return obj(conf[CONF_NUMBER], conf.get(CONF_MODE)) - return obj(conf[CONF_NUMBER], RawExpression(conf.get(CONF_MODE, default_mode)), - conf[CONF_INVERTED]) + if len(conf) == 1: + return IntLiteral(number) + mode = RawExpression(conf.get(CONF_MODE, default_mode)) + return mock_obj(number, mode, inverted) -def exp_gpio_pin(conf): - return GPIOPin(conf[CONF_NUMBER], conf[CONF_MODE], conf.get(CONF_INVERTED)) +def gpio_output_pin_expression(conf): + return generic_gpio_pin_expression_(conf, GPIOOutputPin, 'OUTPUT') -def exp_gpio_output_pin(conf): - return exp_gpio_pin_(GPIOOutputPin, conf, u'OUTPUT') - - -def exp_gpio_input_pin(conf): - return exp_gpio_pin_(GPIOInputPin, conf, u'INPUT') +def gpio_input_pin_expression(conf): + return generic_gpio_pin_expression_(conf, GPIOInputPin, 'INPUT') def setup_mqtt_component(obj, config): diff --git a/esphomeyaml/pins.py b/esphomeyaml/pins.py index ed6c305b72..0b0ea5351a 100644 --- a/esphomeyaml/pins.py +++ b/esphomeyaml/pins.py @@ -177,6 +177,29 @@ def pin_mode(value): raise vol.Invalid(u"Invalid ESP platform.") +GPIO_FULL_OUTPUT_PIN_SCHEMA = vol.Schema({ + vol.Required(CONF_NUMBER): output_pin, + vol.Optional(CONF_MODE): pin_mode, + vol.Optional(CONF_INVERTED): cv.boolean, +}) + +GPIO_FULL_INPUT_PIN_SCHEMA = vol.Schema({ + vol.Required(CONF_NUMBER): output_pin, + vol.Optional(CONF_MODE): pin_mode, + vol.Optional(CONF_INVERTED): cv.boolean, +}) + + +def shorthand_output_pin(value): + value = output_pin(value) + return {CONF_NUMBER: value} + + +def shorthand_input_pin(value): + value = input_pin(value) + return {CONF_NUMBER: value} + + PCF8574_OUTPUT_PIN_SCHEMA = vol.Schema({ vol.Required(CONF_PCF8574): cv.variable_id, vol.Required(CONF_NUMBER): vol.Coerce(int), @@ -184,28 +207,13 @@ PCF8574_OUTPUT_PIN_SCHEMA = vol.Schema({ }) PCF8574_INPUT_PIN_SCHEMA = PCF8574_OUTPUT_PIN_SCHEMA.extend({ - vol.Optional(CONF_MODE, default='INPUT'): vol.All(vol.Upper, vol.Any("INPUT", "INPUT_PULLUP")), + vol.Optional(CONF_MODE): vol.All(vol.Upper, vol.Any("INPUT", "INPUT_PULLUP")), }) -GPIO_OUTPUT_PIN_SCHEMA = vol.Any(output_pin, PCF8574_OUTPUT_PIN_SCHEMA, vol.Schema({ - vol.Required(CONF_NUMBER): output_pin, - vol.Optional(CONF_MODE): pin_mode, - vol.Optional(CONF_INVERTED): cv.boolean, -})) +GPIO_INTERNAL_OUTPUT_PIN_SCHEMA = vol.Any(shorthand_output_pin, GPIO_FULL_OUTPUT_PIN_SCHEMA) -GPIO_INPUT_PIN_SCHEMA = vol.Any(input_pin, PCF8574_INPUT_PIN_SCHEMA, vol.Schema({ - vol.Required(CONF_NUMBER): input_pin, - vol.Optional(CONF_MODE): pin_mode, - vol.Optional(CONF_INVERTED): cv.boolean, -})) +GPIO_OUTPUT_PIN_SCHEMA = vol.Any(PCF8574_OUTPUT_PIN_SCHEMA, GPIO_INTERNAL_OUTPUT_PIN_SCHEMA) +GPIO_INTERNAL_INPUT_PIN_SCHEMA = vol.Any(shorthand_input_pin, GPIO_FULL_INPUT_PIN_SCHEMA) -def schema_validate_number(validator): - def valid(value): - if isinstance(value, dict): - value[CONF_NUMBER] = validator(value[CONF_NUMBER]) - else: - value = validator(value) - return value - - return valid +GPIO_INPUT_PIN_SCHEMA = vol.Any(PCF8574_INPUT_PIN_SCHEMA, GPIO_INTERNAL_INPUT_PIN_SCHEMA) From 9af30061cbc023d4b31d9e5694f188e43f383a50 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 17 May 2018 21:31:39 +0200 Subject: [PATCH 2/2] More filters --- esphomeyaml/components/sensor/__init__.py | 38 +++++++++++++++++++++-- esphomeyaml/config_validation.py | 26 +++++----------- esphomeyaml/const.py | 5 +++ 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/esphomeyaml/components/sensor/__init__.py b/esphomeyaml/components/sensor/__init__.py index bd14c3c744..a33269fbbf 100644 --- a/esphomeyaml/components/sensor/__init__.py +++ b/esphomeyaml/components/sensor/__init__.py @@ -4,7 +4,8 @@ import esphomeyaml.config_validation as cv from esphomeyaml.const import CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_EXPIRE_AFTER, \ CONF_EXPONENTIAL_MOVING_AVERAGE, CONF_FILTERS, CONF_FILTER_NAN, CONF_FILTER_OUT, CONF_ICON, \ CONF_LAMBDA, CONF_MQTT_ID, CONF_MULTIPLY, CONF_NAME, CONF_OFFSET, CONF_SEND_EVERY, \ - CONF_SLIDING_WINDOW_MOVING_AVERAGE, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, CONF_ID + CONF_SLIDING_WINDOW_MOVING_AVERAGE, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, CONF_ID, \ + CONF_THROTTLE, CONF_DELTA, CONF_OR, CONF_AND, CONF_UNIQUE from esphomeyaml.helpers import App, ArrayInitializer, MockObj, Pvariable, RawExpression, add, \ setup_mqtt_component @@ -12,6 +13,12 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ }) + +def validate_recursive_filter(value): + print(value) + return FILTERS_SCHEMA(value) + + FILTERS_SCHEMA = vol.All(cv.ensure_list, [vol.Any( vol.Schema({vol.Required(CONF_OFFSET): vol.Coerce(float)}), vol.Schema({vol.Required(CONF_MULTIPLY): vol.Coerce(float)}), @@ -30,6 +37,13 @@ FILTERS_SCHEMA = vol.All(cv.ensure_list, [vol.Any( }) }), vol.Schema({vol.Required(CONF_LAMBDA): cv.string_strict}), + vol.Schema({vol.Required(CONF_THROTTLE): cv.positive_time_period_milliseconds}), + vol.Schema({vol.Required(CONF_DELTA): vol.Coerce(float)}), + vol.Schema({vol.Required(CONF_UNIQUE): None}), + + # No ensure_list here as OR/AND filters only make sense with multiple children + vol.Schema({vol.Required(CONF_OR): validate_recursive_filter}), + vol.Schema({vol.Required(CONF_AND): validate_recursive_filter}), )]) MQTT_SENSOR_SCHEMA = vol.Schema({ @@ -53,6 +67,11 @@ FilterOutNANFilter = MockObj('new sensor::FilterOutNANFilter') SlidingWindowMovingAverageFilter = MockObj('new sensor::SlidingWindowMovingAverageFilter') ExponentialMovingAverageFilter = MockObj('new sensor::ExponentialMovingAverageFilter') LambdaFilter = MockObj('new sensor::LambdaFilter') +ThrottleFilter = MockObj('new sensor::ThrottleFilter') +DeltaFilter = MockObj('new sensor::DeltaFilter') +OrFilter = MockObj('new sensor::OrFilter') +AndFilter = MockObj('new sensor::AndFilter') +UniqueFilter = MockObj('new sensor::UniqueFilter') def setup_filter(config): @@ -73,9 +92,23 @@ def setup_filter(config): if CONF_LAMBDA in config: s = u'[](float x) -> Optional {{ return {}; }}'.format(config[CONF_LAMBDA]) return LambdaFilter(RawExpression(s)) + if CONF_THROTTLE in config: + return ThrottleFilter(config[CONF_THROTTLE]) + if CONF_DELTA in config: + return DeltaFilter(config[CONF_DELTA]) + if CONF_OR in config: + return OrFilter(setup_filters(config[CONF_OR])) + if CONF_AND in config: + return OrFilter(setup_filters(config[CONF_AND])) + if CONF_UNIQUE in config: + return UniqueFilter() raise ValueError(u"Filter unsupported: {}".format(config)) +def setup_filters(config): + return ArrayInitializer(*[setup_filter(x) for x in config]) + + def setup_mqtt_sensor_component(obj, config): if CONF_EXPIRE_AFTER in config: if config[CONF_EXPIRE_AFTER] is None: @@ -93,8 +126,7 @@ def setup_sensor(obj, config): if CONF_ACCURACY_DECIMALS in config: add(obj.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) if CONF_FILTERS in config: - filters = [setup_filter(x) for x in config[CONF_FILTERS]] - add(obj.set_filters(ArrayInitializer(*filters))) + add(obj.set_filters(setup_filters(config[CONF_FILTERS]))) def register_sensor(var, config): diff --git a/esphomeyaml/config_validation.py b/esphomeyaml/config_validation.py index 5d8b3bfa11..455a5e125b 100644 --- a/esphomeyaml/config_validation.py +++ b/esphomeyaml/config_validation.py @@ -209,13 +209,6 @@ def time_period_str_colon(value): elif not isinstance(value, str): raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) - negative_offset = False - if value.startswith('-'): - negative_offset = True - value = value[1:] - elif value.startswith('+'): - value = value[1:] - try: parsed = [int(x) for x in value.split(':')] except ValueError: @@ -229,12 +222,7 @@ def time_period_str_colon(value): else: raise vol.Invalid(TIME_PERIOD_ERROR.format(value)) - offset = TimePeriod(hours=hour, minutes=minute, seconds=second) - - if negative_offset: - offset *= -1 - - return offset + return TimePeriod(hours=hour, minutes=minute, seconds=second) def time_period_str_unit(value): @@ -277,17 +265,17 @@ def time_period_str_unit(value): return TimePeriod(**{kwarg: float(match.group(1))}) -def time_period_in_milliseconds(value): +def time_period_in_milliseconds_(value): if value.microseconds is not None and value.microseconds != 0: raise vol.Invalid("Maximum precision is milliseconds") return TimePeriodMilliseconds(**value.as_dict()) -def time_period_in_microseconds(value): +def time_period_in_microseconds_(value): return TimePeriodMicroseconds(**value.as_dict()) -def time_period_in_seconds(value): +def time_period_in_seconds_(value): if value.microseconds is not None and value.microseconds != 0: raise vol.Invalid("Maximum precision is seconds") if value.milliseconds is not None and value.milliseconds != 0: @@ -297,9 +285,9 @@ def time_period_in_seconds(value): time_period = vol.Any(time_period_str_unit, time_period_str_colon, time_period_dict) 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_microseconds = vol.All(positive_time_period, time_period_in_microseconds) +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_microseconds = vol.All(positive_time_period, time_period_in_microseconds_) positive_not_null_time_period = vol.All(time_period, vol.Range(min=TimePeriod(), min_included=False)) diff --git a/esphomeyaml/const.py b/esphomeyaml/const.py index ecde750020..69fb837a76 100644 --- a/esphomeyaml/const.py +++ b/esphomeyaml/const.py @@ -109,6 +109,11 @@ CONF_WINDOW_SIZE = 'window_size' CONF_SEND_EVERY = 'send_every' CONF_ALPHA = 'alpha' CONF_LAMBDA = 'lambda' +CONF_THROTTLE = 'throttle' +CONF_DELTA = 'delta' +CONF_OR = 'or' +CONF_AND = 'and' +CONF_UNIQUE = 'unique' CONF_UPDATE_INTERVAL = 'update_interval' CONF_PULL_MODE = 'pull_mode' CONF_COUNT_MODE = 'count_mode'