From af4e2bf61d0b1ed049e4cad50891a688879c816f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 26 Oct 2018 22:57:03 +0200 Subject: [PATCH] Add Stepper Motor Support (#206) * Add stepper support * Fix output set_level * Lint --- esphomeyaml/components/output/__init__.py | 6 +- esphomeyaml/components/stepper/__init__.py | 126 +++++++++++++++++++++ esphomeyaml/components/stepper/a4988.py | 35 ++++++ esphomeyaml/const.py | 8 ++ esphomeyaml/helpers.py | 1 + tests/test1.yaml | 29 +++++ 6 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 esphomeyaml/components/stepper/__init__.py create mode 100644 esphomeyaml/components/stepper/a4988.py diff --git a/esphomeyaml/components/output/__init__.py b/esphomeyaml/components/output/__init__.py index 33e275dd34..3cf0bd6e94 100644 --- a/esphomeyaml/components/output/__init__.py +++ b/esphomeyaml/components/output/__init__.py @@ -5,7 +5,7 @@ import esphomeyaml.config_validation as cv from esphomeyaml.components.power_supply import PowerSupplyComponent from esphomeyaml.const import CONF_INVERTED, CONF_MAX_POWER, CONF_POWER_SUPPLY, CONF_ID, CONF_LEVEL from esphomeyaml.helpers import add, esphomelib_ns, get_variable, TemplateArguments, Pvariable, \ - templatable, bool_ + templatable, float_ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ @@ -85,7 +85,7 @@ def output_turn_off_to_code(config, action_id, arg_type): CONF_OUTPUT_SET_LEVEL = 'output.set_level' OUTPUT_SET_LEVEL_ACTION = vol.Schema({ vol.Required(CONF_ID): cv.use_variable_id(None), - vol.Required(CONF_LEVEL): cv.percentage, + vol.Required(CONF_LEVEL): cv.templatable(cv.percentage), }) @@ -97,7 +97,7 @@ def output_set_level_to_code(config, action_id, arg_type): rhs = var.make_set_level_action(template_arg) type = SetLevelAction.template(arg_type) action = Pvariable(action_id, rhs, type=type) - for template_ in templatable(config[CONF_LEVEL], arg_type, bool_): + for template_ in templatable(config[CONF_LEVEL], arg_type, float_): yield None add(action.set_level(template_)) yield action diff --git a/esphomeyaml/components/stepper/__init__.py b/esphomeyaml/components/stepper/__init__.py new file mode 100644 index 0000000000..3b5092d5cd --- /dev/null +++ b/esphomeyaml/components/stepper/__init__.py @@ -0,0 +1,126 @@ +import voluptuous as vol + +from esphomeyaml.automation import ACTION_REGISTRY +import esphomeyaml.config_validation as cv +from esphomeyaml.const import CONF_ACCELERATION, CONF_DECELERATION, CONF_ID, CONF_MAX_SPEED, \ + CONF_POSITION, CONF_TARGET +from esphomeyaml.helpers import Pvariable, TemplateArguments, add, add_job, esphomelib_ns, \ + get_variable, int32, templatable + +PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ + +}) + +# pylint: disable=invalid-name +stepper_ns = esphomelib_ns.namespace('stepper') +Stepper = stepper_ns.Stepper + +SetTargetAction = stepper_ns.SetTargetAction +ReportPositionAction = stepper_ns.ReportPositionAction + + +def validate_acceleration(value): + value = cv.string(value) + for suffix in ('steps/s^2', 'steps/s*s', 'steps/s/s', 'steps/ss', 'steps/(s*s)'): + if value.endswith(suffix): + value = value[:-len(suffix)] + + if value == 'inf': + return 1e6 + + try: + value = float(value) + except ValueError: + raise vol.Invalid("Expected acceleration as floating point number, got {}".format(value)) + + if value <= 0: + raise vol.Invalid("Acceleration must be larger than 0 steps/s^2!") + + return value + + +def validate_speed(value): + value = cv.string(value) + for suffix in ('steps/s', 'steps/s'): + if value.endswith(suffix): + value = value[:-len(suffix)] + + if value == 'inf': + return 1e6 + + try: + value = float(value) + except ValueError: + raise vol.Invalid("Expected speed as floating point number, got {}".format(value)) + + if value <= 0: + raise vol.Invalid("Speed must be larger than 0 steps/s!") + + return value + + +STEPPER_SCHEMA = vol.Schema({ + cv.GenerateID(): cv.declare_variable_id(Stepper), + vol.Required(CONF_MAX_SPEED): validate_speed, + vol.Optional(CONF_ACCELERATION): validate_acceleration, + vol.Optional(CONF_DECELERATION): validate_acceleration, +}) + +STEPPER_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(STEPPER_SCHEMA.schema) + + +def setup_stepper_core_(stepper_var, config): + if CONF_ACCELERATION in config: + add(stepper_var.set_acceleration(config[CONF_ACCELERATION])) + if CONF_DECELERATION in config: + add(stepper_var.set_deceleration(config[CONF_DECELERATION])) + if CONF_MAX_SPEED in config: + add(stepper_var.set_max_speed(config[CONF_MAX_SPEED])) + + +def setup_stepper(stepper_var, config): + add_job(setup_stepper_core_, stepper_var, config) + + +BUILD_FLAGS = '-DUSE_STEPPER' + +CONF_STEPPER_SET_TARGET = 'stepper.set_target' +STEPPER_SET_TARGET_ACTION_SCHEMA = vol.Schema({ + vol.Required(CONF_ID): cv.use_variable_id(None), + vol.Required(CONF_TARGET): cv.templatable(cv.int_), +}) + + +@ACTION_REGISTRY.register(CONF_STEPPER_SET_TARGET, STEPPER_SET_TARGET_ACTION_SCHEMA) +def stepper_set_target_to_code(config, action_id, arg_type): + template_arg = TemplateArguments(arg_type) + for var in get_variable(config[CONF_ID]): + yield None + rhs = var.make_set_target_action(template_arg) + type = SetTargetAction.template(arg_type) + action = Pvariable(action_id, rhs, type=type) + for template_ in templatable(config[CONF_TARGET], arg_type, int32): + yield None + add(action.set_target(template_)) + yield action + + +CONF_STEPPER_REPORT_POSITION = 'stepper.report_position' +STEPPER_REPORT_POSITION_ACTION_SCHEMA = vol.Schema({ + vol.Required(CONF_ID): cv.use_variable_id(None), + vol.Required(CONF_POSITION): cv.templatable(cv.int_), +}) + + +@ACTION_REGISTRY.register(CONF_STEPPER_REPORT_POSITION, STEPPER_REPORT_POSITION_ACTION_SCHEMA) +def stepper_report_position_to_code(config, action_id, arg_type): + template_arg = TemplateArguments(arg_type) + for var in get_variable(config[CONF_ID]): + yield None + rhs = var.make_report_position_action(template_arg) + type = ReportPositionAction.template(arg_type) + action = Pvariable(action_id, rhs, type=type) + for template_ in templatable(config[CONF_POSITION], arg_type, int32): + yield None + add(action.set_target(template_)) + yield action diff --git a/esphomeyaml/components/stepper/a4988.py b/esphomeyaml/components/stepper/a4988.py new file mode 100644 index 0000000000..e14d4a4a5b --- /dev/null +++ b/esphomeyaml/components/stepper/a4988.py @@ -0,0 +1,35 @@ +import voluptuous as vol + +from esphomeyaml import pins +from esphomeyaml.components import stepper +import esphomeyaml.config_validation as cv +from esphomeyaml.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN +from esphomeyaml.helpers import App, Pvariable, add, gpio_output_pin_expression + +A4988 = stepper.stepper_ns.A4988 + +PLATFORM_SCHEMA = stepper.STEPPER_PLATFORM_SCHEMA.extend({ + vol.Required(CONF_ID): cv.declare_variable_id(A4988), + vol.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema, + vol.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema, + vol.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema, +}) + + +def to_code(config): + for step_pin in gpio_output_pin_expression(config[CONF_STEP_PIN]): + yield + for dir_pin in gpio_output_pin_expression(config[CONF_DIR_PIN]): + yield + rhs = App.make_a4988(step_pin, dir_pin) + a4988 = Pvariable(config[CONF_ID], rhs) + + if CONF_SLEEP_PIN in config: + for sleep_pin in gpio_output_pin_expression(config[CONF_SLEEP_PIN]): + yield + add(a4988.set_sleep_pin(sleep_pin)) + + stepper.setup_stepper(a4988, config) + + +BUILD_FLAGS = '-DUSE_A4988' diff --git a/esphomeyaml/const.py b/esphomeyaml/const.py index 1858110a7c..539e0e4ba0 100644 --- a/esphomeyaml/const.py +++ b/esphomeyaml/const.py @@ -350,6 +350,14 @@ CONF_ARGS = 'args' CONF_FORMAT = 'format' CONF_COLOR_CORRECT = 'color_correct' CONF_ON_JSON_MESSAGE = 'on_json_message' +CONF_ACCELERATION = 'acceleration' +CONF_DECELERATION = 'deceleration' +CONF_MAX_SPEED = 'max_speed' +CONF_TARGET = 'target' +CONF_POSITION = 'position' +CONF_STEP_PIN = 'step_pin' +CONF_DIR_PIN = 'dir_pin' +CONF_SLEEP_PIN = 'sleep_pin' ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_' ARDUINO_VERSION_ESP32_DEV = 'https://github.com/platformio/platform-espressif32.git#feature/stage' diff --git a/esphomeyaml/helpers.py b/esphomeyaml/helpers.py index 3f517fdce2..6ef894bdfe 100644 --- a/esphomeyaml/helpers.py +++ b/esphomeyaml/helpers.py @@ -565,6 +565,7 @@ std_string = std_ns.string uint8 = global_ns.namespace('uint8_t') uint16 = global_ns.namespace('uint16_t') uint32 = global_ns.namespace('uint32_t') +int32 = global_ns.namespace('int32_t') const_char_p = global_ns.namespace('const char *') NAN = global_ns.namespace('NAN') esphomelib_ns = global_ns # using namespace esphomelib; diff --git a/tests/test1.yaml b/tests/test1.yaml index 6961bd2598..d25abda030 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -797,6 +797,12 @@ switch: optimistic: True turn_on_action: - switch.turn_on: living_room_lights_on + - output.set_level: + id: gpio_19 + level: 50% + - output.set_level: + id: gpio_19 + level: !lambda 'return 0.5;' turn_off_action: - switch.turn_on: living_room_lights_off - platform: restart @@ -831,6 +837,19 @@ switch: - platform: uart name: "UART Bytes Output" data: [0xDE, 0xAD, 0xBE, 0xEF] + - platform: template + optimistic: true + name: Stepper Switch + turn_on_action: + - stepper.set_target: + id: my_stepper + target: !lambda |- + static int32_t i = 0; + i += 1000; + if (i > 5000) { + i = -5000; + } + return i; fan: - platform: binary @@ -941,3 +960,13 @@ pcf8574: - id: 'pcf8574_hub' address: 0x21 pcf8575: False + +stepper: +- platform: a4988 + id: my_stepper + step_pin: GPIO23 + dir_pin: GPIO24 + sleep_pin: GPIO25 + max_speed: 250 steps/s + acceleration: 100 steps/s^2 + deceleration: 200 steps/s^2