From a78eff68152a4932807a95940881da7677dea0f8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 May 2021 09:44:31 +1200 Subject: [PATCH] 1.17.0 (#1740) --- .github/PULL_REQUEST_TEMPLATE.md | 40 +- .github/workflows/ci-docker.yml | 4 +- .github/workflows/ci.yml | 18 - .github/workflows/release-dev.yml | 22 +- .github/workflows/release.yml | 22 +- .pre-commit-config.yaml | 30 +- CODEOWNERS | 15 +- docker/Dockerfile | 8 +- docker/Dockerfile.dev | 2 +- docker/Dockerfile.hassio | 6 +- docker/Dockerfile.lint | 8 +- docker/platformio_install_deps.py | 20 + esphome/__main__.py | 466 +- esphome/api/api_pb2.py | 5862 +++++++++++------ esphome/api/client.py | 73 +- esphome/automation.py | 179 +- esphome/codegen.py | 79 +- esphome/components/a4988/a4988.cpp | 6 + esphome/components/a4988/a4988.h | 1 + esphome/components/a4988/stepper.py | 18 +- esphome/components/ac_dimmer/output.py | 38 +- esphome/components/adalight/__init__.py | 16 +- .../adalight/adalight_light_effect.cpp | 6 +- .../adalight/adalight_light_effect.h | 2 +- esphome/components/adc/__init__.py | 2 +- esphome/components/adc/adc_sensor.cpp | 2 + esphome/components/adc/sensor.py | 51 +- .../components/addressable_light/__init__.py | 0 .../addressable_light_display.cpp | 67 + .../addressable_light_display.h | 59 + .../components/addressable_light/display.py | 63 + esphome/components/ade7953/sensor.py | 74 +- esphome/components/ads1115/__init__.py | 24 +- esphome/components/ads1115/sensor.py | 70 +- esphome/components/aht10/sensor.py | 38 +- esphome/components/am2320/sensor.py | 40 +- esphome/components/animation/__init__.py | 54 +- esphome/components/apds9960/__init__.py | 22 +- esphome/components/apds9960/binary_sensor.py | 24 +- esphome/components/apds9960/sensor.py | 26 +- esphome/components/api/__init__.py | 180 +- esphome/components/api/api.proto | 12 +- esphome/components/api/api_connection.cpp | 23 +- esphome/components/api/api_pb2.cpp | 48 + esphome/components/api/api_pb2.h | 5 + esphome/components/as3935/__init__.py | 56 +- esphome/components/as3935/binary_sensor.py | 10 +- esphome/components/as3935/sensor.py | 31 +- esphome/components/as3935_i2c/__init__.py | 20 +- esphome/components/as3935_spi/__init__.py | 20 +- esphome/components/async_tcp/__init__.py | 6 +- .../components/atc_mithermometer/sensor.py | 69 +- esphome/components/atm90e32/sensor.py | 133 +- esphome/components/bang_bang/__init__.py | 2 +- esphome/components/bang_bang/climate.py | 72 +- esphome/components/bh1750/sensor.py | 41 +- esphome/components/binary/__init__.py | 2 +- esphome/components/binary/fan/__init__.py | 24 +- esphome/components/binary/fan/binary_fan.cpp | 2 +- esphome/components/binary/light/__init__.py | 12 +- esphome/components/binary_sensor/__init__.py | 370 +- .../components/binary_sensor_map/sensor.py | 48 +- .../components/ble_presence/binary_sensor.py | 42 +- esphome/components/ble_rssi/sensor.py | 49 +- esphome/components/ble_scanner/text_sensor.py | 25 +- esphome/components/bme280/sensor.py | 110 +- esphome/components/bme680/sensor.py | 150 +- esphome/components/bmp085/sensor.py | 40 +- esphome/components/bmp280/sensor.py | 91 +- esphome/components/canbus/__init__.py | 127 +- esphome/components/captive_portal/__init__.py | 24 +- esphome/components/ccs811/sensor.py | 54 +- esphome/components/climate/__init__.py | 143 +- esphome/components/climate_ir/__init__.py | 51 +- esphome/components/climate_ir_lg/climate.py | 41 +- .../climate_ir_lg/climate_ir_lg.cpp | 70 +- .../components/climate_ir_lg/climate_ir_lg.h | 11 + esphome/components/color/__init__.py | 62 +- esphome/components/coolix/climate.py | 16 +- esphome/components/cover/__init__.py | 108 +- esphome/components/cse7766/sensor.py | 48 +- esphome/components/ct_clamp/sensor.py | 36 +- esphome/components/custom/__init__.py | 2 +- .../custom/binary_sensor/__init__.py | 21 +- esphome/components/custom/climate/__init__.py | 22 +- esphome/components/custom/cover/__init__.py | 22 +- esphome/components/custom/light/__init__.py | 22 +- esphome/components/custom/output/__init__.py | 62 +- esphome/components/custom/sensor/__init__.py | 17 +- esphome/components/custom/switch/__init__.py | 26 +- .../components/custom/text_sensor/__init__.py | 28 +- .../components/custom_component/__init__.py | 25 +- esphome/components/cwww/light.py | 33 +- esphome/components/daikin/climate.py | 14 +- esphome/components/dallas/__init__.py | 22 +- esphome/components/dallas/sensor.py | 34 +- esphome/components/debug/__init__.py | 16 +- esphome/components/deep_sleep/__init__.py | 133 +- .../deep_sleep/deep_sleep_component.h | 10 +- esphome/components/dfplayer/__init__.py | 281 +- esphome/components/dht/__init__.py | 2 +- esphome/components/dht/sensor.py | 59 +- esphome/components/dht12/sensor.py | 38 +- esphome/components/display/__init__.py | 93 +- esphome/components/display/display_buffer.cpp | 2 +- esphome/components/display/display_buffer.h | 2 +- .../components/display/display_color_utils.h | 110 + esphome/components/ds1307/time.py | 52 +- esphome/components/duty_cycle/sensor.py | 31 +- esphome/components/e131/__init__.py | 48 +- .../e131/e131_addressable_light_effect.cpp | 13 +- .../e131/e131_addressable_light_effect.h | 2 +- esphome/components/endstop/cover.py | 61 +- .../components/esp32_ble_beacon/__init__.py | 30 +- .../components/esp32_ble_tracker/__init__.py | 191 +- esphome/components/esp32_camera/__init__.py | 199 +- esphome/components/esp32_dac/output.py | 18 +- esphome/components/esp32_hall/sensor.py | 26 +- esphome/components/esp32_touch/__init__.py | 111 +- .../components/esp32_touch/binary_sensor.py | 38 +- esphome/components/esp8266_pwm/output.py | 42 +- esphome/components/ethernet/__init__.py | 117 +- .../exposure_notifications/__init__.py | 36 +- esphome/components/ezo/sensor.py | 22 +- esphome/components/fan/__init__.py | 116 +- esphome/components/fan/automation.h | 2 +- esphome/components/fan/fan_helpers.cpp | 20 + esphome/components/fan/fan_helpers.h | 11 + esphome/components/fan/fan_state.cpp | 31 +- esphome/components/fan/fan_state.h | 19 +- esphome/components/fan/fan_traits.h | 11 +- esphome/components/fastled_base/__init__.py | 44 +- esphome/components/fastled_clockless/light.py | 70 +- esphome/components/fastled_spi/light.py | 56 +- esphome/components/font/__init__.py | 64 +- esphome/components/fujitsu_general/climate.py | 16 +- .../fujitsu_general/fujitsu_general.cpp | 436 +- .../fujitsu_general/fujitsu_general.h | 24 +- esphome/components/globals/__init__.py | 49 +- esphome/components/gpio/__init__.py | 4 +- .../components/gpio/binary_sensor/__init__.py | 14 +- esphome/components/gpio/output/__init__.py | 13 +- esphome/components/gpio/switch/__init__.py | 35 +- esphome/components/gps/__init__.py | 24 +- esphome/components/gps/time/__init__.py | 16 +- esphome/components/hbridge/light.py | 18 +- esphome/components/hdc1080/sensor.py | 40 +- esphome/components/hitachi_ac344/climate.py | 14 +- esphome/components/hlw8012/sensor.py | 86 +- esphome/components/hm3301/sensor.py | 90 +- esphome/components/hmc5883l/sensor.py | 92 +- esphome/components/homeassistant/__init__.py | 4 +- .../homeassistant/binary_sensor/__init__.py | 18 +- .../homeassistant/sensor/__init__.py | 27 +- .../homeassistant/text_sensor/__init__.py | 17 +- .../components/homeassistant/time/__init__.py | 14 +- esphome/components/http_request/__init__.py | 184 +- .../components/http_request/http_request.cpp | 5 +- .../components/http_request/http_request.h | 14 +- esphome/components/htu21d/sensor.py | 40 +- esphome/components/hx711/sensor.py | 35 +- esphome/components/i2c/__init__.py | 40 +- esphome/components/ili9341/display.py | 62 +- .../components/ili9341/ili9341_display.cpp | 6 +- esphome/components/image/__init__.py | 61 +- esphome/components/ina219/sensor.py | 75 +- esphome/components/ina226/sensor.py | 69 +- esphome/components/ina3221/sensor.py | 87 +- .../inkbird_ibsth1_mini/__init__.py | 0 .../inkbird_ibsth1_mini.cpp | 86 + .../inkbird_ibsth1_mini/inkbird_ibsth1_mini.h | 34 + .../components/inkbird_ibsth1_mini/sensor.py | 62 + esphome/components/inkplate6/__init__.py | 2 +- esphome/components/inkplate6/display.py | 154 +- esphome/components/inkplate6/inkplate.cpp | 187 +- esphome/components/integration/__init__.py | 2 +- esphome/components/integration/sensor.py | 63 +- esphome/components/interval/__init__.py | 21 +- esphome/components/json/__init__.py | 8 +- esphome/components/lcd_base/__init__.py | 12 +- esphome/components/lcd_base/lcd_display.cpp | 9 +- esphome/components/lcd_gpio/display.py | 48 +- esphome/components/lcd_pcf8574/display.py | 26 +- esphome/components/ledc/__init__.py | 2 +- esphome/components/ledc/output.py | 69 +- esphome/components/light/__init__.py | 150 +- .../components/light/addressable_light.cpp | 17 +- esphome/components/light/addressable_light.h | 183 +- .../light/addressable_light_effect.h | 46 +- esphome/components/light/automation.py | 222 +- esphome/components/light/effects.py | 432 +- esphome/components/light/types.py | 87 +- esphome/components/logger/__init__.py | 222 +- esphome/components/max31855/sensor.py | 33 +- esphome/components/max31856/sensor.py | 38 +- esphome/components/max31865/sensor.py | 52 +- esphome/components/max6675/sensor.py | 22 +- esphome/components/max7219/display.py | 42 +- esphome/components/max7219digit/display.py | 81 +- esphome/components/mcp23008/__init__.py | 59 +- esphome/components/mcp23008/mcp23008.cpp | 69 +- esphome/components/mcp23008/mcp23008.h | 58 +- esphome/components/mcp23016/__init__.py | 69 +- esphome/components/mcp23017/__init__.py | 59 +- esphome/components/mcp23017/mcp23017.cpp | 74 +- esphome/components/mcp23017/mcp23017.h | 71 +- esphome/components/mcp23s08/__init__.py | 62 +- esphome/components/mcp23s08/mcp23s08.cpp | 96 +- esphome/components/mcp23s08/mcp23s08.h | 54 +- esphome/components/mcp23s17/__init__.py | 62 +- esphome/components/mcp23s17/mcp23s17.cpp | 82 +- esphome/components/mcp23s17/mcp23s17.h | 68 +- esphome/components/mcp23x08_base/__init__.py | 8 + .../mcp23x08_base/mcp23x08_base.cpp | 89 + .../components/mcp23x08_base/mcp23x08_base.h | 39 + esphome/components/mcp23x17_base/__init__.py | 8 + .../mcp23x17_base/mcp23x17_base.cpp | 93 + .../components/mcp23x17_base/mcp23x17_base.h | 52 + esphome/components/mcp23xxx_base/__init__.py | 118 + .../mcp23xxx_base/mcp23xxx_base.cpp | 21 + .../components/mcp23xxx_base/mcp23xxx_base.h | 55 + esphome/components/mcp2515/canbus.py | 38 +- esphome/components/mcp3008/__init__.py | 18 +- esphome/components/mcp3008/sensor.py | 36 +- esphome/components/mcp4725/__init__.py | 0 esphome/components/mcp4725/mcp4725.cpp | 38 + esphome/components/mcp4725/mcp4725.h | 23 + esphome/components/mcp4725/output.py | 26 + esphome/components/mcp9808/sensor.py | 26 +- esphome/components/mhz19/sensor.py | 74 +- esphome/components/midea_ac/__init__.py | 0 esphome/components/midea_ac/climate.py | 69 + esphome/components/midea_ac/midea_climate.cpp | 110 + esphome/components/midea_ac/midea_climate.h | 47 + esphome/components/midea_ac/midea_frame.cpp | 160 + esphome/components/midea_ac/midea_frame.h | 137 + esphome/components/midea_dongle/__init__.py | 30 + .../components/midea_dongle/midea_dongle.cpp | 98 + .../components/midea_dongle/midea_dongle.h | 56 + .../components/midea_dongle/midea_frame.cpp | 95 + esphome/components/midea_dongle/midea_frame.h | 104 + esphome/components/mitsubishi/climate.py | 14 +- esphome/components/modbus/__init__.py | 22 +- esphome/components/monochromatic/light.py | 16 +- esphome/components/mpr121/__init__.py | 34 +- esphome/components/mpr121/binary_sensor.py | 29 +- esphome/components/mpu6050/sensor.py | 93 +- esphome/components/mqtt/__init__.py | 330 +- esphome/components/mqtt/mqtt_fan.cpp | 6 +- esphome/components/mqtt_subscribe/__init__.py | 2 +- .../mqtt_subscribe/sensor/__init__.py | 35 +- .../mqtt_subscribe/text_sensor/__init__.py | 23 +- esphome/components/ms5611/sensor.py | 42 +- esphome/components/my9231/__init__.py | 34 +- esphome/components/my9231/output.py | 19 +- esphome/components/neopixelbus/light.py | 176 +- .../neopixelbus/neopixelbus_light.h | 3 +- esphome/components/network/__init__.py | 2 +- esphome/components/nextion/__init__.py | 2 +- esphome/components/nextion/binary_sensor.py | 29 +- esphome/components/nextion/display.py | 27 +- esphome/components/nfc/__init__.py | 6 +- esphome/components/ntc/sensor.py | 113 +- esphome/components/ota/__init__.py | 45 +- esphome/components/output/__init__.py | 80 +- esphome/components/output/switch/__init__.py | 12 +- esphome/components/packages/__init__.py | 7 +- esphome/components/partition/light.py | 45 +- esphome/components/pca9685/__init__.py | 23 +- esphome/components/pca9685/output.py | 19 +- esphome/components/pcd8544/display.py | 43 +- esphome/components/pcf8574/__init__.py | 76 +- esphome/components/pid/__init__.py | 2 +- esphome/components/pid/climate.py | 127 +- esphome/components/pid/sensor/__init__.py | 51 +- esphome/components/pmsx003/sensor.py | 107 +- esphome/components/pn532/__init__.py | 75 +- esphome/components/pn532/binary_sensor.py | 34 +- .../pn532/pn532_mifare_ultralight.cpp | 7 +- esphome/components/pn532_i2c/__init__.py | 20 +- esphome/components/pn532_spi/__init__.py | 20 +- esphome/components/power_supply/__init__.py | 26 +- esphome/components/prometheus/__init__.py | 20 +- esphome/components/pulse_counter/sensor.py | 87 +- esphome/components/pulse_meter/__init__.py | 0 .../pulse_meter/pulse_meter_sensor.cpp | 87 + .../pulse_meter/pulse_meter_sensor.h | 42 + esphome/components/pulse_meter/sensor.py | 75 + esphome/components/pulse_width/sensor.py | 25 +- esphome/components/pzem004t/sensor.py | 54 +- esphome/components/pzemac/sensor.py | 69 +- esphome/components/pzemdc/sensor.py | 45 +- esphome/components/qmc5883l/sensor.py | 87 +- esphome/components/rc522/__init__.py | 34 +- esphome/components/rc522/binary_sensor.py | 34 +- esphome/components/rc522_i2c/__init__.py | 20 +- esphome/components/rc522_spi/__init__.py | 20 +- esphome/components/rc522_spi/binary_sensor.py | 2 +- esphome/components/rdm6300/__init__.py | 34 +- esphome/components/rdm6300/binary_sensor.py | 20 +- esphome/components/remote_base/__init__.py | 868 ++- .../remote_base/samsung36_protocol.cpp | 103 + .../remote_base/samsung36_protocol.h | 39 + .../components/remote_receiver/__init__.py | 57 +- .../remote_receiver/binary_sensor.py | 2 +- .../components/remote_transmitter/__init__.py | 24 +- .../components/remote_transmitter/switch.py | 27 +- esphome/components/resistance/sensor.py | 38 +- esphome/components/restart/__init__.py | 2 +- esphome/components/restart/switch.py | 19 +- esphome/components/rf_bridge/__init__.py | 135 +- esphome/components/rgb/light.py | 18 +- esphome/components/rgbw/light.py | 24 +- esphome/components/rgbww/light.py | 53 +- esphome/components/rgbww/rgbww_light_output.h | 8 +- esphome/components/rotary_encoder/sensor.py | 116 +- esphome/components/rtttl/__init__.py | 78 +- esphome/components/ruuvi_ble/__init__.py | 16 +- esphome/components/ruuvitag/sensor.py | 109 +- esphome/components/scd30/sensor.py | 77 +- esphome/components/script/__init__.py | 95 +- esphome/components/sds011/sensor.py | 60 +- esphome/components/senseair/senseair.cpp | 73 +- esphome/components/senseair/senseair.h | 59 +- esphome/components/senseair/sensor.py | 80 +- esphome/components/sensor/__init__.py | 487 +- esphome/components/sensor/filter.cpp | 62 + esphome/components/sensor/filter.h | 60 + esphome/components/sensor/sensor.cpp | 7 + esphome/components/sensor/sensor.h | 19 + esphome/components/servo/__init__.py | 74 +- esphome/components/servo/servo.cpp | 2 +- esphome/components/sgp30/sensor.py | 83 +- esphome/components/sht3xd/sensor.py | 40 +- esphome/components/shtcx/sensor.py | 48 +- esphome/components/shutdown/__init__.py | 2 +- esphome/components/shutdown/switch.py | 20 +- esphome/components/sim800l/__init__.py | 84 +- esphome/components/sim800l/sim800l.cpp | 26 + esphome/components/sim800l/sim800l.h | 20 +- esphome/components/sm16716/__init__.py | 31 +- esphome/components/sm16716/output.py | 18 +- esphome/components/sm300d2/__init__.py | 0 esphome/components/sm300d2/sensor.py | 92 + esphome/components/sm300d2/sm300d2.cpp | 99 + esphome/components/sm300d2/sm300d2.h | 38 + esphome/components/sn74hc595/__init__.py | 57 +- esphome/components/sntp/time.py | 23 +- esphome/components/speed/__init__.py | 2 +- esphome/components/speed/fan/__init__.py | 41 +- esphome/components/speed/fan/speed_fan.cpp | 10 +- esphome/components/speed/fan/speed_fan.h | 12 +- esphome/components/spi/__init__.py | 35 +- esphome/components/spi/spi.cpp | 2 +- esphome/components/spi/spi.h | 58 +- esphome/components/sps30/sensor.py | 104 +- esphome/components/ssd1306_base/__init__.py | 48 +- esphome/components/ssd1306_i2c/display.py | 22 +- esphome/components/ssd1306_spi/display.py | 24 +- esphome/components/ssd1322_base/__init__.py | 36 +- .../components/ssd1322_base/ssd1322_base.cpp | 4 +- esphome/components/ssd1322_spi/display.py | 26 +- esphome/components/ssd1325_base/__init__.py | 44 +- .../components/ssd1325_base/ssd1325_base.cpp | 4 +- esphome/components/ssd1325_spi/display.py | 26 +- esphome/components/ssd1327_base/__init__.py | 25 +- .../components/ssd1327_base/ssd1327_base.cpp | 4 +- esphome/components/ssd1327_i2c/display.py | 24 +- esphome/components/ssd1327_spi/display.py | 26 +- esphome/components/ssd1331_base/__init__.py | 19 +- .../components/ssd1331_base/ssd1331_base.cpp | 4 +- esphome/components/ssd1331_spi/display.py | 26 +- esphome/components/ssd1351_base/__init__.py | 27 +- .../components/ssd1351_base/ssd1351_base.cpp | 4 +- esphome/components/ssd1351_spi/display.py | 26 +- esphome/components/st7735/__init__.py | 3 +- esphome/components/st7735/display.py | 97 +- esphome/components/st7735/st7735.cpp | 12 +- esphome/components/st7789v/__init__.py | 2 +- esphome/components/st7789v/display.py | 47 +- esphome/components/st7789v/st7789v.cpp | 2 +- esphome/components/status/binary_sensor.py | 20 +- esphome/components/status_led/__init__.py | 16 +- esphome/components/stepper/__init__.py | 87 +- esphome/components/sts3x/sensor.py | 24 +- esphome/components/substitutions/__init__.py | 56 +- esphome/components/sun/__init__.py | 109 +- esphome/components/sun/sensor/__init__.py | 34 +- .../components/sun/text_sensor/__init__.py | 43 +- esphome/components/switch/__init__.py | 88 +- esphome/components/sx1509/__init__.py | 108 +- .../sx1509/binary_sensor/__init__.py | 22 +- esphome/components/sx1509/output/__init__.py | 19 +- esphome/components/tcl112/climate.py | 16 +- esphome/components/tcs34725/sensor.py | 101 +- esphome/components/teleinfo/__init__.py | 2 +- esphome/components/teleinfo/sensor.py | 42 +- esphome/components/template/__init__.py | 2 +- .../template/binary_sensor/__init__.py | 36 +- esphome/components/template/cover/__init__.py | 123 +- .../components/template/output/__init__.py | 49 +- .../components/template/sensor/__init__.py | 47 +- .../components/template/switch/__init__.py | 63 +- .../template/text_sensor/__init__.py | 34 +- esphome/components/text_sensor/__init__.py | 67 +- esphome/components/thermostat/climate.py | 408 +- esphome/components/time/__init__.py | 271 +- esphome/components/time_based/cover.py | 60 +- esphome/components/tlc59208f/__init__.py | 18 +- esphome/components/tlc59208f/output.py | 19 +- esphome/components/tm1637/display.py | 44 +- esphome/components/tm1651/__init__.py | 96 +- esphome/components/tmp102/sensor.py | 26 +- esphome/components/tmp117/sensor.py | 31 +- esphome/components/toshiba/climate.py | 14 +- .../components/total_daily_energy/sensor.py | 22 +- esphome/components/tsl2561/sensor.py | 49 +- esphome/components/ttp229_bsf/__init__.py | 22 +- .../components/ttp229_bsf/binary_sensor.py | 16 +- esphome/components/ttp229_lsf/__init__.py | 24 +- .../components/ttp229_lsf/binary_sensor.py | 16 +- esphome/components/tuya/__init__.py | 26 +- .../components/tuya/binary_sensor/__init__.py | 22 +- esphome/components/tuya/climate/__init__.py | 107 +- esphome/components/tuya/fan/__init__.py | 26 +- esphome/components/tuya/fan/tuya_fan.cpp | 16 +- esphome/components/tuya/light/__init__.py | 51 +- esphome/components/tuya/sensor/__init__.py | 18 +- esphome/components/tuya/switch/__init__.py | 18 +- esphome/components/tuya/tuya.cpp | 116 +- esphome/components/tuya/tuya.h | 13 +- esphome/components/tx20/sensor.py | 41 +- esphome/components/uart/__init__.py | 99 +- esphome/components/uart/switch/__init__.py | 28 +- .../components/uart/switch/uart_switch.cpp | 32 +- esphome/components/uart/switch/uart_switch.h | 6 + esphome/components/uart/uart.h | 6 + esphome/components/uart/uart_esp32.cpp | 2 +- esphome/components/uln2003/stepper.py | 49 +- esphome/components/ultrasonic/__init__.py | 2 +- esphome/components/ultrasonic/sensor.py | 61 +- esphome/components/uptime/sensor.py | 18 +- esphome/components/version/__init__.py | 2 +- esphome/components/version/text_sensor.py | 18 +- esphome/components/vl53l0x/sensor.py | 70 +- esphome/components/vl53l0x/vl53l0x_sensor.cpp | 63 +- esphome/components/vl53l0x/vl53l0x_sensor.h | 13 + .../components/voltage_sampler/__init__.py | 4 +- .../components/waveshare_epaper/display.py | 121 +- .../waveshare_epaper/waveshare_epaper.cpp | 177 +- .../waveshare_epaper/waveshare_epaper.h | 3 + esphome/components/web_server/__init__.py | 61 +- esphome/components/web_server/web_server.cpp | 19 +- .../components/web_server_base/__init__.py | 24 +- esphome/components/whirlpool/climate.py | 24 +- esphome/components/wifi/__init__.py | 252 +- esphome/components/wifi/wpa2_eap.py | 51 +- esphome/components/wifi_info/text_sensor.py | 64 +- esphome/components/wifi_signal/sensor.py | 27 +- esphome/components/wled/__init__.py | 15 +- esphome/components/wled/wled_light_effect.cpp | 12 +- esphome/components/wled/wled_light_effect.h | 2 +- esphome/components/xiaomi_ble/__init__.py | 16 +- esphome/components/xiaomi_cgd1/sensor.py | 56 +- esphome/components/xiaomi_cgg1/sensor.py | 50 +- esphome/components/xiaomi_gcls002/sensor.py | 64 +- esphome/components/xiaomi_hhccjcy01/sensor.py | 70 +- .../components/xiaomi_hhccpot002/sensor.py | 47 +- esphome/components/xiaomi_jqjcy01ym/sensor.py | 65 +- esphome/components/xiaomi_lywsd02/sensor.py | 52 +- .../components/xiaomi_lywsd03mmc/sensor.py | 67 +- esphome/components/xiaomi_lywsdcgq/sensor.py | 52 +- esphome/components/xiaomi_mhoc401/sensor.py | 59 +- esphome/components/xiaomi_miscale/__init__.py | 0 esphome/components/xiaomi_miscale/sensor.py | 44 + .../xiaomi_miscale/xiaomi_miscale.cpp | 101 + .../xiaomi_miscale/xiaomi_miscale.h | 37 + .../components/xiaomi_miscale2/__init__.py | 0 esphome/components/xiaomi_miscale2/sensor.py | 53 + .../xiaomi_miscale2/xiaomi_miscale2.cpp | 116 + .../xiaomi_miscale2/xiaomi_miscale2.h | 40 + .../xiaomi_mjyd02yla/binary_sensor.py | 77 +- .../xiaomi_mue4094rt/binary_sensor.py | 36 +- .../components/xiaomi_wx08zm/binary_sensor.py | 57 +- esphome/components/yashima/climate.py | 26 +- esphome/components/zyaura/sensor.py | 54 +- esphome/config.py | 350 +- esphome/config_helpers.py | 21 +- esphome/config_validation.py | 725 +- esphome/const.py | 1329 ++-- esphome/core.py | 210 +- esphome/core/application.h | 8 +- esphome/core/color.h | 139 +- esphome/core/helpers.cpp | 7 + esphome/core/helpers.h | 1 + esphome/core_config.py | 257 +- esphome/cpp_generator.py | 272 +- esphome/cpp_helpers.py | 20 +- esphome/cpp_types.py | 56 +- esphome/dashboard/dashboard.py | 327 +- esphome/espota2.py | 117 +- esphome/helpers.py | 79 +- esphome/jsonschema.py | 91 + esphome/legacy.py | 2 +- esphome/mqtt.py | 66 +- esphome/pins.py | 1218 +++- esphome/platformio_api.py | 123 +- esphome/storage_json.py | 133 +- esphome/util.py | 60 +- esphome/voluptuous_schema.py | 55 +- esphome/vscode.py | 46 +- esphome/wizard.py | 245 +- esphome/writer.py | 233 +- esphome/yaml_util.py | 161 +- esphome/zeroconf.py | 144 +- platformio.ini | 2 +- pylintrc | 1 + pyproject.toml | 3 + requirements.txt | 12 +- requirements_test.txt | 10 +- script/api_protobuf/api_options_pb2.py | 300 +- script/api_protobuf/api_protobuf.py | 544 +- script/build_codeowners.py | 39 +- script/build_compile_commands.py | 2 +- script/build_jsonschema.py | 742 +++ script/bump-docker-base-version.py | 20 +- script/bump-version.py | 29 +- script/ci-custom.py | 467 +- script/component_test | 9 + script/fulltest | 1 + script/helpers.py | 95 +- script/lint-python | 46 +- script/setup | 2 + setup.cfg | 39 + setup.py | 69 +- .../binary_sensor/test_binary_sensor.py | 16 +- tests/component_tests/conftest.py | 12 +- tests/component_tests/sensor/test_sensor.py | 14 + tests/component_tests/sensor/test_sensor.yaml | 12 + tests/test1.yaml | 225 +- tests/test2.yaml | 28 + tests/test3.yaml | 36 +- tests/test4.yaml | 61 + tests/unit_tests/conftest.py | 1 - tests/unit_tests/strategies.py | 4 +- tests/unit_tests/test_codegen.py | 88 +- tests/unit_tests/test_config_validation.py | 24 +- tests/unit_tests/test_core.py | 373 +- tests/unit_tests/test_cpp_generator.py | 150 +- tests/unit_tests/test_cpp_helpers.py | 19 +- tests/unit_tests/test_helpers.py | 139 +- tests/unit_tests/test_pins.py | 87 +- tests/unit_tests/test_wizard.py | 42 +- 553 files changed, 27780 insertions(+), 14199 deletions(-) create mode 100755 docker/platformio_install_deps.py create mode 100644 esphome/components/addressable_light/__init__.py create mode 100644 esphome/components/addressable_light/addressable_light_display.cpp create mode 100644 esphome/components/addressable_light/addressable_light_display.h create mode 100644 esphome/components/addressable_light/display.py create mode 100644 esphome/components/display/display_color_utils.h create mode 100644 esphome/components/fan/fan_helpers.cpp create mode 100644 esphome/components/fan/fan_helpers.h create mode 100644 esphome/components/inkbird_ibsth1_mini/__init__.py create mode 100644 esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp create mode 100644 esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h create mode 100644 esphome/components/inkbird_ibsth1_mini/sensor.py create mode 100644 esphome/components/mcp23x08_base/__init__.py create mode 100644 esphome/components/mcp23x08_base/mcp23x08_base.cpp create mode 100644 esphome/components/mcp23x08_base/mcp23x08_base.h create mode 100644 esphome/components/mcp23x17_base/__init__.py create mode 100644 esphome/components/mcp23x17_base/mcp23x17_base.cpp create mode 100644 esphome/components/mcp23x17_base/mcp23x17_base.h create mode 100644 esphome/components/mcp23xxx_base/__init__.py create mode 100644 esphome/components/mcp23xxx_base/mcp23xxx_base.cpp create mode 100644 esphome/components/mcp23xxx_base/mcp23xxx_base.h create mode 100644 esphome/components/mcp4725/__init__.py create mode 100644 esphome/components/mcp4725/mcp4725.cpp create mode 100644 esphome/components/mcp4725/mcp4725.h create mode 100644 esphome/components/mcp4725/output.py create mode 100644 esphome/components/midea_ac/__init__.py create mode 100644 esphome/components/midea_ac/climate.py create mode 100644 esphome/components/midea_ac/midea_climate.cpp create mode 100644 esphome/components/midea_ac/midea_climate.h create mode 100644 esphome/components/midea_ac/midea_frame.cpp create mode 100644 esphome/components/midea_ac/midea_frame.h create mode 100644 esphome/components/midea_dongle/__init__.py create mode 100644 esphome/components/midea_dongle/midea_dongle.cpp create mode 100644 esphome/components/midea_dongle/midea_dongle.h create mode 100644 esphome/components/midea_dongle/midea_frame.cpp create mode 100644 esphome/components/midea_dongle/midea_frame.h create mode 100644 esphome/components/pulse_meter/__init__.py create mode 100644 esphome/components/pulse_meter/pulse_meter_sensor.cpp create mode 100644 esphome/components/pulse_meter/pulse_meter_sensor.h create mode 100644 esphome/components/pulse_meter/sensor.py create mode 100644 esphome/components/remote_base/samsung36_protocol.cpp create mode 100644 esphome/components/remote_base/samsung36_protocol.h create mode 100644 esphome/components/sm300d2/__init__.py create mode 100644 esphome/components/sm300d2/sensor.py create mode 100644 esphome/components/sm300d2/sm300d2.cpp create mode 100644 esphome/components/sm300d2/sm300d2.h create mode 100644 esphome/components/xiaomi_miscale/__init__.py create mode 100644 esphome/components/xiaomi_miscale/sensor.py create mode 100644 esphome/components/xiaomi_miscale/xiaomi_miscale.cpp create mode 100644 esphome/components/xiaomi_miscale/xiaomi_miscale.h create mode 100644 esphome/components/xiaomi_miscale2/__init__.py create mode 100644 esphome/components/xiaomi_miscale2/sensor.py create mode 100644 esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp create mode 100644 esphome/components/xiaomi_miscale2/xiaomi_miscale2.h create mode 100644 esphome/jsonschema.py create mode 100644 pyproject.toml create mode 100644 script/build_jsonschema.py create mode 100755 script/component_test create mode 100644 tests/component_tests/sensor/test_sensor.py create mode 100644 tests/component_tests/sensor/test_sensor.yaml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 94a5b7284e00..0d77eee7aa17 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,13 +1,47 @@ -## Description: +# What does this implement/fix? +Quick description -**Related issue (if applicable):** fixes +## Types of changes + +- [ ] Bugfix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Configuration change (this will require users to update their yaml configuration files to keep working) + +**Related issue or feature (if applicable):** fixes **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs# + +# Test Environment + +- [ ] ESP32 +- [ ] ESP8266 +- [ ] Windows +- [ ] Mac OS +- [ ] Linux + +## Example entry for `config.yaml`: + + +```yaml +# Example config.yaml + +``` + +# Explain your changes + +Describe your changes here to communicate to the maintainers **why we should accept this pull request**. +Very important to fill if no issue linked ## Checklist: - [ ] The code change is tested and works locally. - [ ] Tests have been added to verify that the new code works (under `tests/` folder). - + If user exposed functionality or configuration variables are added/changed: - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs). diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index e7642c6434eb..7da8a3f8f1e8 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v2 - name: Set up env variables run: | - base_version="2.6.0" + base_version="3.0.0" if [[ "${{ matrix.build_type }}" == "hassio" ]]; then build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" @@ -45,7 +45,7 @@ jobs: run: | docker pull "${BUILD_TO}:dev" || true - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes + run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - run: | docker build \ --build-arg "BUILD_FROM=${BUILD_FROM}" \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8136b0678e58..ed4343202c56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,15 +18,6 @@ jobs: container: esphome/esphome-lint:latest steps: - uses: actions/checkout@v2 - # Cache platformio intermediary files (like libraries etc) - # Note: platformio platform versions should be cached via the esphome-lint image - - name: Cache Platformio - uses: actions/cache@v1 - with: - path: .pio - key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} - restore-keys: | - lint-cpp-pio- # Set up the pio project so that the cpp checks know how files are compiled # (build flags, libraries etc) - name: Set up platformio environment @@ -49,15 +40,6 @@ jobs: split: [1, 2, 3, 4] steps: - uses: actions/checkout@v2 - # Cache platformio intermediary files (like libraries etc) - # Note: platformio platform versions should be cached via the esphome-lint image - - name: Cache Platformio - uses: actions/cache@v1 - with: - path: .pio - key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} - restore-keys: | - lint-cpp-pio- # Set up the pio project so that the cpp checks know how files are compiled # (build flags, libraries etc) - name: Set up platformio environment diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index 22ac278af73b..b4c4a8f17ddb 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -15,15 +15,6 @@ jobs: container: esphome/esphome-lint:latest steps: - uses: actions/checkout@v2 - # Cache platformio intermediary files (like libraries etc) - # Note: platformio platform versions should be cached via the esphome-lint image - - name: Cache Platformio - uses: actions/cache@v1 - with: - path: .pio - key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} - restore-keys: | - lint-cpp-pio- # Set up the pio project so that the cpp checks know how files are compiled # (build flags, libraries etc) - name: Set up platformio environment @@ -46,15 +37,6 @@ jobs: split: [1, 2, 3, 4] steps: - uses: actions/checkout@v2 - # Cache platformio intermediary files (like libraries etc) - # Note: platformio platform versions should be cached via the esphome-lint image - - name: Cache Platformio - uses: actions/cache@v1 - with: - path: .pio - key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} - restore-keys: | - lint-cpp-pio- # Set up the pio project so that the cpp checks know how files are compiled # (build flags, libraries etc) - name: Set up platformio environment @@ -192,7 +174,7 @@ jobs: echo "TAG=${TAG}" >> $GITHUB_ENV - name: Set up env variables run: | - base_version="2.6.0" + base_version="3.0.0" if [[ "${{ matrix.build_type }}" == "hassio" ]]; then build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" @@ -211,7 +193,7 @@ jobs: run: | docker pull "${BUILD_TO}:dev" || true - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes + run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - run: | docker build \ --build-arg "BUILD_FROM=${BUILD_FROM}" \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bbaefe4ea61c..a1fd2dba2444 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,15 +14,6 @@ jobs: container: esphome/esphome-lint:latest steps: - uses: actions/checkout@v2 - # Cache platformio intermediary files (like libraries etc) - # Note: platformio platform versions should be cached via the esphome-lint image - - name: Cache Platformio - uses: actions/cache@v1 - with: - path: .pio - key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} - restore-keys: | - lint-cpp-pio- # Set up the pio project so that the cpp checks know how files are compiled # (build flags, libraries etc) - name: Set up platformio environment @@ -45,15 +36,6 @@ jobs: split: [1, 2, 3, 4] steps: - uses: actions/checkout@v2 - # Cache platformio intermediary files (like libraries etc) - # Note: platformio platform versions should be cached via the esphome-lint image - - name: Cache Platformio - uses: actions/cache@v1 - with: - path: .pio - key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} - restore-keys: | - lint-cpp-pio- # Set up the pio project so that the cpp checks know how files are compiled # (build flags, libraries etc) - name: Set up platformio environment @@ -212,7 +194,7 @@ jobs: echo "TAG=${TAG}" >> $GITHUB_ENV - name: Set up env variables run: | - base_version="2.6.0" + base_version="3.0.0" if [[ "${{ matrix.build_type }}" == "hassio" ]]; then build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" @@ -239,7 +221,7 @@ jobs: run: | docker pull "${BUILD_TO}:${CACHE_TAG}" || true - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes + run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - run: | docker build \ --build-arg "BUILD_FROM=${BUILD_FROM}" \ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d57da791fdc4..1e56aa17cf40 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,27 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 + - repo: https://github.com/ambv/black + rev: 20.8b1 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files - - id: flake8 + - id: black + args: + - --safe + - --quiet + files: ^((esphome|script|tests)/.+)?[^/]+\.py$ + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 + additional_dependencies: + - flake8-docstrings==1.5.0 + - pydocstyle==5.1.1 + files: ^(esphome|tests)/.+\.py$ + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: no-commit-to-branch + args: + - --branch=dev + - --branch=master + - --branch=beta diff --git a/CODEOWNERS b/CODEOWNERS index 0106b87fa8e7..0a1f2b3ed269 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -13,6 +13,7 @@ esphome/core/* @esphome/core # Integrations esphome/components/ac_dimmer/* @glmnet esphome/components/adc/* @esphome/core +esphome/components/addressable_light/* @justfalter esphome/components/animation/* @syndlex esphome/components/api/* @OttoWinter esphome/components/async_tcp/* @OttoWinter @@ -37,6 +38,7 @@ esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/homeassistant/* @OttoWinter esphome/components/i2c/* @esphome/core +esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkplate6/* @jesserockz esphome/components/integration/* @OttoWinter esphome/components/interval/* @esphome/core @@ -44,10 +46,18 @@ esphome/components/json/* @OttoWinter esphome/components/ledc/* @OttoWinter esphome/components/light/* @esphome/core esphome/components/logger/* @esphome/core -esphome/components/mcp23s08/* @SenexCrenshaw -esphome/components/mcp23s17/* @SenexCrenshaw +esphome/components/max7219digit/* @rspaargaren +esphome/components/mcp23008/* @jesserockz +esphome/components/mcp23017/* @jesserockz +esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz +esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz +esphome/components/mcp23x08_base/* @jesserockz +esphome/components/mcp23x17_base/* @jesserockz +esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp9808/* @k7hpn +esphome/components/midea_ac/* @dudanov +esphome/components/midea_dongle/* @dudanov esphome/components/network/* @esphome/core esphome/components/nfc/* @jesserockz esphome/components/ota/* @esphome/core @@ -57,6 +67,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/power_supply/* @esphome/core +esphome/components/pulse_meter/* @stevebaxter esphome/components/rc522/* @glmnet esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet diff --git a/docker/Dockerfile b/docker/Dockerfile index bbee4b24346a..7364299ba7ab 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,9 +1,11 @@ -ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0 +ARG BUILD_FROM=esphome/esphome-base-amd64:3.0.0 FROM ${BUILD_FROM} # First install requirements to leverage caching when requirements don't change -COPY requirements.txt / -RUN pip3 install --no-cache-dir -r /requirements.txt +COPY requirements.txt docker/platformio_install_deps.py platformio.ini / +RUN \ + pip3 install --no-cache-dir -r /requirements.txt \ + && /platformio_install_deps.py /platformio.ini # Then copy esphome and install COPY . . diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 989802ab1ee8..52495fc8ae04 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM esphome/esphome-base-amd64:2.6.0 +FROM esphome/esphome-base-amd64:3.0.0 COPY . . diff --git a/docker/Dockerfile.hassio b/docker/Dockerfile.hassio index eb7ef2300130..b6ff037c3513 100644 --- a/docker/Dockerfile.hassio +++ b/docker/Dockerfile.hassio @@ -2,8 +2,10 @@ ARG BUILD_FROM FROM ${BUILD_FROM} # First install requirements to leverage caching when requirements don't change -COPY requirements.txt / -RUN pip3 install --no-cache-dir -r /requirements.txt +COPY requirements.txt docker/platformio_install_deps.py platformio.ini / +RUN \ + pip3 install --no-cache-dir -r /requirements.txt \ + && /platformio_install_deps.py /platformio.ini # Copy root filesystem COPY docker/rootfs/ / diff --git a/docker/Dockerfile.lint b/docker/Dockerfile.lint index 27f04dd33d27..5488e68c91f6 100644 --- a/docker/Dockerfile.lint +++ b/docker/Dockerfile.lint @@ -1,7 +1,9 @@ -FROM esphome/esphome-lint-base:2.6.0 +FROM esphome/esphome-lint-base:3.0.0 -COPY requirements.txt requirements_test.txt / -RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt +COPY requirements.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / +RUN \ + pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt \ + && /platformio_install_deps.py /platformio.ini VOLUME ["/esphome"] WORKDIR /esphome diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py new file mode 100755 index 000000000000..6f3e9f28d5ad --- /dev/null +++ b/docker/platformio_install_deps.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# This script is used in the docker containers to preinstall +# all platformio libraries in the global storage + +import configparser +import re +import subprocess +import sys + +config = configparser.ConfigParser() +config.read(sys.argv[1]) +libs = [] +for line in config['common']['lib_deps'].splitlines(): + # Format: '1655@1.0.2 ; TinyGPSPlus (has name conflict)' (includes comment) + m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line) + if m is None: + continue + libs.append(m.group(1)) + +subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) diff --git a/esphome/__main__.py b/esphome/__main__.py index 200ab2d7d7ad..20cb44d11cd1 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -8,21 +8,36 @@ 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, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS +from esphome.const import ( + CONF_BAUD_RATE, + CONF_BROKER, + CONF_LOGGER, + CONF_OTA, + CONF_PASSWORD, + CONF_PORT, + CONF_ESPHOME, + CONF_PLATFORMIO_OPTIONS, +) from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority from esphome.helpers import color, indent -from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \ - get_serial_ports +from esphome.util import ( + run_external_command, + run_external_process, + safe_print, + list_yaml_files, + get_serial_ports, +) _LOGGER = logging.getLogger(__name__) def choose_prompt(options): if not options: - raise EsphomeError("Found no valid options for upload/logging, please make sure relevant " - "sections (ota, api, mqtt, ...) are in your configuration and/or the " - "device is plugged in.") + raise EsphomeError( + "Found no valid options for upload/logging, please make sure relevant " + "sections (ota, api, mqtt, ...) are in your configuration and/or the " + "device is plugged in." + ) if len(options) == 1: return options[0][1] @@ -32,7 +47,7 @@ def choose_prompt(options): safe_print(f" [{i+1}] {desc}") while True: - opt = input('(number): ') + opt = input("(number): ") if opt in options: opt = options.index(opt) break @@ -42,7 +57,7 @@ def choose_prompt(options): raise ValueError break except ValueError: - safe_print(color('red', f"Invalid option: '{opt}'")) + safe_print(color("red", f"Invalid option: '{opt}'")) return options[opt - 1][1] @@ -50,14 +65,14 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api options = [] for port in get_serial_ports(): options.append((f"{port.path} ({port.description})", port.path)) - if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config): + if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config): options.append((f"Over The Air ({CORE.address})", CORE.address)) - if default == 'OTA': + if default == "OTA": return CORE.address - if show_mqtt and 'mqtt' in CORE.config: - options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT')) - if default == 'OTA': - return 'MQTT' + if show_mqtt and "mqtt" in CORE.config: + options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT")) + if default == "OTA": + return "MQTT" if default is not None: return default if check_default is not None and check_default in [opt[1] for opt in options]: @@ -66,11 +81,11 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api def get_port_type(port): - if port.startswith('/') or port.startswith('COM'): - return 'SERIAL' - if port == 'MQTT': - return 'MQTT' - return 'NETWORK' + if port.startswith("/") or port.startswith("COM"): + return "SERIAL" + if port == "MQTT": + return "MQTT" + return "NETWORK" def run_miniterm(config, port): @@ -80,7 +95,7 @@ def run_miniterm(config, port): if CONF_LOGGER not in config: _LOGGER.info("Logger is not enabled. Not starting UART logs.") return - baud_rate = config['logger'][CONF_BAUD_RATE] + baud_rate = config["logger"][CONF_BAUD_RATE] if baud_rate == 0: _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.") _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate) @@ -93,13 +108,18 @@ def run_miniterm(config, port): except serial.SerialException: _LOGGER.error("Serial port closed!") return - line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace') - time = datetime.now().time().strftime('[%H:%M:%S]') + line = ( + raw.replace(b"\r", b"") + .replace(b"\n", b"") + .decode("utf8", "backslashreplace") + ) + time = datetime.now().time().strftime("[%H:%M:%S]") message = time + line safe_print(message) backtrace_state = platformio_api.process_stacktrace( - config, line, backtrace_state=backtrace_state) + config, line, backtrace_state=backtrace_state + ) def wrap_to_code(name, comp): @@ -111,7 +131,7 @@ def wrapped(conf): cg.add(cg.LineComment(f"{name}:")) if comp.config_schema is not None: conf_str = yaml_util.dump(conf) - conf_str = conf_str.replace('//', '') + conf_str = conf_str.replace("//", "") cg.add(cg.LineComment(indent(conf_str))) yield coro(conf) @@ -151,15 +171,31 @@ def compile_program(args, config): def upload_using_esptool(config, port): path = CORE.firmware_bin - first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800) + first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get( + "upload_speed", 460800 + ) def run_esptool(baud_rate): - cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset', - '--baud', str(baud_rate), - '--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path] - - if os.environ.get('ESPHOME_USE_SUBPROCESS') is None: + cmd = [ + "esptool.py", + "--before", + "default_reset", + "--after", + "hard_reset", + "--baud", + str(baud_rate), + "--chip", + "esp8266", + "--port", + port, + "write_flash", + "0x0", + path, + ] + + if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: import esptool + # pylint: disable=protected-access return run_external_command(esptool._main, *cmd) @@ -169,14 +205,16 @@ def run_esptool(baud_rate): if rc == 0 or first_baudrate == 115200: return rc # Try with 115200 baud rate, with some serial chips the faster baud rates do not work well - _LOGGER.info("Upload with baud rate %s failed. Trying again with baud rate 115200.", - first_baudrate) + _LOGGER.info( + "Upload with baud rate %s failed. Trying again with baud rate 115200.", + first_baudrate, + ) return run_esptool(115200) def upload_program(config, args, host): # if upload is to a serial port use platformio, otherwise assume ota - if get_port_type(host) == 'SERIAL': + if get_port_type(host) == "SERIAL": from esphome import platformio_api if CORE.is_esp8266: @@ -186,8 +224,10 @@ def upload_program(config, args, host): from esphome import espota2 if CONF_OTA not in config: - raise EsphomeError("Cannot upload Over the Air as the config does not include the ota: " - "component") + raise EsphomeError( + "Cannot upload Over the Air as the config does not include the ota: " + "component" + ) ota_conf = config[CONF_OTA] remote_port = ota_conf[CONF_PORT] @@ -196,19 +236,21 @@ def upload_program(config, args, host): def show_logs(config, args, port): - if 'logger' not in config: + if "logger" not in config: raise EsphomeError("Logger is not configured!") - if get_port_type(port) == 'SERIAL': + if get_port_type(port) == "SERIAL": run_miniterm(config, port) return 0 - if get_port_type(port) == 'NETWORK' and 'api' in config: + if get_port_type(port) == "NETWORK" and "api" in config: from esphome.api.client import run_logs return run_logs(config, port) - if get_port_type(port) == 'MQTT' and 'mqtt' in config: + if get_port_type(port) == "MQTT" and "mqtt" in config: from esphome import mqtt - return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) + return mqtt.show_logs( + config, args.topic, args.username, args.password, args.client_id + ) raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)") @@ -216,7 +258,9 @@ def show_logs(config, args, port): def clean_mqtt(config, args): from esphome import mqtt - return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id) + return mqtt.clear_topic( + config, args.topic, args.username, args.password, args.client_id + ) def setup_log(debug=False, quiet=False): @@ -230,27 +274,31 @@ def setup_log(debug=False, quiet=False): logging.basicConfig(level=log_level) fmt = "%(levelname)s %(message)s" colorfmt = f"%(log_color)s{fmt}%(reset)s" - datefmt = '%H:%M:%S' + datefmt = "%H:%M:%S" - logging.getLogger('urllib3').setLevel(logging.WARNING) + logging.getLogger("urllib3").setLevel(logging.WARNING) try: import colorama + colorama.init(strip=True) from colorlog import ColoredFormatter - logging.getLogger().handlers[0].setFormatter(ColoredFormatter( - colorfmt, - datefmt=datefmt, - reset=True, - log_colors={ - 'DEBUG': 'cyan', - 'INFO': 'green', - 'WARNING': 'yellow', - 'ERROR': 'red', - 'CRITICAL': 'red', - } - )) + + logging.getLogger().handlers[0].setFormatter( + ColoredFormatter( + colorfmt, + datefmt=datefmt, + reset=True, + log_colors={ + "DEBUG": "cyan", + "INFO": "green", + "WARNING": "yellow", + "ERROR": "red", + "CRITICAL": "red", + }, + ) + ) except ImportError: pass @@ -272,6 +320,8 @@ def command_config(args, config): def command_vscode(args): from esphome import vscode + logging.disable(logging.INFO) + logging.disable(logging.WARNING) CORE.config_path = args.configuration[0] vscode.read_config(args) @@ -291,8 +341,13 @@ def command_compile(args, config): def command_upload(args, config): - port = choose_upload_log_host(default=args.upload_port, check_default=None, - show_ota=True, show_mqtt=False, show_api=False) + port = choose_upload_log_host( + default=args.upload_port, + check_default=None, + show_ota=True, + show_mqtt=False, + show_api=False, + ) exit_code = upload_program(config, args, port) if exit_code != 0: return exit_code @@ -301,8 +356,13 @@ def command_upload(args, config): def command_logs(args, config): - port = choose_upload_log_host(default=args.serial_port, check_default=None, - show_ota=False, show_mqtt=True, show_api=True) + port = choose_upload_log_host( + default=args.serial_port, + check_default=None, + show_ota=False, + show_mqtt=True, + show_api=True, + ) return show_logs(config, args, port) @@ -314,16 +374,26 @@ def command_run(args, config): if exit_code != 0: return exit_code _LOGGER.info("Successfully compiled program.") - port = choose_upload_log_host(default=args.upload_port, check_default=None, - show_ota=True, show_mqtt=False, show_api=True) + port = choose_upload_log_host( + default=args.upload_port, + check_default=None, + show_ota=True, + show_mqtt=False, + show_api=True, + ) exit_code = upload_program(config, args, port) if exit_code != 0: return exit_code _LOGGER.info("Successfully uploaded program.") if args.no_logs: return 0 - port = choose_upload_log_host(default=args.upload_port, check_default=port, - show_ota=False, show_mqtt=True, show_api=True) + port = choose_upload_log_host( + default=args.upload_port, + check_default=port, + show_ota=False, + show_mqtt=True, + show_api=True, + ) return show_logs(config, args, port) @@ -372,137 +442,189 @@ def print_bar(middle_text): click.echo(f"{half_line}{middle_text}{half_line}") for f in files: - print("Updating {}".format(color('cyan', f))) - print('-' * twidth) + print("Updating {}".format(color("cyan", f))) + print("-" * twidth) print() - rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs', '--upload-port', - 'OTA') + rc = run_external_process( + "esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA" + ) if rc == 0: - print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f)) + print_bar("[{}] {}".format(color("bold_green", "SUCCESS"), f)) success[f] = True else: - print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f)) + print_bar("[{}] {}".format(color("bold_red", "ERROR"), f)) success[f] = False print() print() print() - print_bar('[{}]'.format(color('bold_white', 'SUMMARY'))) + print_bar("[{}]".format(color("bold_white", "SUMMARY"))) failed = 0 for f in files: if success[f]: - print(" - {}: {}".format(f, color('green', 'SUCCESS'))) + print(" - {}: {}".format(f, color("green", "SUCCESS"))) else: - print(" - {}: {}".format(f, color('bold_red', 'FAILED'))) + print(" - {}: {}".format(f, color("bold_red", "FAILED"))) failed += 1 return failed PRE_CONFIG_ACTIONS = { - 'wizard': command_wizard, - 'version': command_version, - 'dashboard': command_dashboard, - 'vscode': command_vscode, - 'update-all': command_update_all, + "wizard": command_wizard, + "version": command_version, + "dashboard": command_dashboard, + "vscode": command_vscode, + "update-all": command_update_all, } POST_CONFIG_ACTIONS = { - 'config': command_config, - 'compile': command_compile, - 'upload': command_upload, - 'logs': command_logs, - 'run': command_run, - 'clean-mqtt': command_clean_mqtt, - 'mqtt-fingerprint': command_mqtt_fingerprint, - 'clean': command_clean, + "config": command_config, + "compile": command_compile, + "upload": command_upload, + "logs": command_logs, + "run": command_run, + "clean-mqtt": command_clean_mqtt, + "mqtt-fingerprint": command_mqtt_fingerprint, + "clean": command_clean, } def parse_args(argv): - parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}') - parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.", - action='store_true') - parser.add_argument('-q', '--quiet', help="Disable all esphome logs.", - action='store_true') - parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true') - parser.add_argument('-s', '--substitution', nargs=2, action='append', - help='Add a substitution', metavar=('key', 'value')) - parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*') - - subparsers = parser.add_subparsers(help='Commands', dest='command') + parser = argparse.ArgumentParser(description=f"ESPHome v{const.__version__}") + parser.add_argument( + "-v", "--verbose", help="Enable verbose esphome logs.", action="store_true" + ) + parser.add_argument( + "-q", "--quiet", help="Disable all esphome logs.", action="store_true" + ) + parser.add_argument("--dashboard", help=argparse.SUPPRESS, action="store_true") + parser.add_argument( + "-s", + "--substitution", + nargs=2, + action="append", + help="Add a substitution", + metavar=("key", "value"), + ) + parser.add_argument( + "configuration", help="Your YAML configuration file.", nargs="*" + ) + + subparsers = parser.add_subparsers(help="Commands", dest="command") subparsers.required = True - subparsers.add_parser('config', help='Validate the configuration and spit it out.') - - parser_compile = subparsers.add_parser('compile', - help='Read the configuration and compile a program.') - parser_compile.add_argument('--only-generate', - help="Only generate source code, do not compile.", - action='store_true') - - parser_upload = subparsers.add_parser('upload', help='Validate the configuration ' - 'and upload the latest binary.') - parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. " - "For example /dev/cu.SLAB_USBtoUART.") - - parser_logs = subparsers.add_parser('logs', help='Validate the configuration ' - 'and show all MQTT logs.') - parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.') - parser_logs.add_argument('--username', help='Manually set the username.') - parser_logs.add_argument('--password', help='Manually set the password.') - parser_logs.add_argument('--client-id', help='Manually set the client id.') - parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use" - "For example /dev/cu.SLAB_USBtoUART.") - - parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, ' - 'upload it, and start MQTT logs.') - parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. " - "For example /dev/cu.SLAB_USBtoUART.") - parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.', - action='store_true') - parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.') - parser_run.add_argument('--username', help='Manually set the MQTT username for logs.') - parser_run.add_argument('--password', help='Manually set the MQTT password for logs.') - parser_run.add_argument('--client-id', help='Manually set the client id for logs.') - - parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from " - "retain messages.") - parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.') - parser_clean.add_argument('--username', help='Manually set the username.') - parser_clean.add_argument('--password', help='Manually set the password.') - parser_clean.add_argument('--client-id', help='Manually set the client id.') - - subparsers.add_parser('wizard', help="A helpful setup wizard that will guide " - "you through setting up esphome.") - - subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.") - - subparsers.add_parser('version', help="Print the esphome version and exit.") - - subparsers.add_parser('clean', help="Delete all temporary build files.") - - dashboard = subparsers.add_parser('dashboard', - help="Create a simple web server for a dashboard.") - dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.", - type=int, default=6052) - dashboard.add_argument("--username", help="The optional username to require " - "for authentication.", - type=str, default='') - dashboard.add_argument("--password", help="The optional password to require " - "for authentication.", - type=str, default='') - dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.", - action='store_true') - dashboard.add_argument("--hassio", - help=argparse.SUPPRESS, - action="store_true") - dashboard.add_argument("--socket", - help="Make the dashboard serve under a unix socket", type=str) - - vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS) - vscode.add_argument('--ace', action='store_true') - - subparsers.add_parser('update-all', help=argparse.SUPPRESS) + subparsers.add_parser("config", help="Validate the configuration and spit it out.") + + parser_compile = subparsers.add_parser( + "compile", help="Read the configuration and compile a program." + ) + parser_compile.add_argument( + "--only-generate", + help="Only generate source code, do not compile.", + action="store_true", + ) + + parser_upload = subparsers.add_parser( + "upload", help="Validate the configuration " "and upload the latest binary." + ) + parser_upload.add_argument( + "--upload-port", + help="Manually specify the upload port to use. " + "For example /dev/cu.SLAB_USBtoUART.", + ) + + parser_logs = subparsers.add_parser( + "logs", help="Validate the configuration " "and show all MQTT logs." + ) + parser_logs.add_argument("--topic", help="Manually set the topic to subscribe to.") + parser_logs.add_argument("--username", help="Manually set the username.") + parser_logs.add_argument("--password", help="Manually set the password.") + parser_logs.add_argument("--client-id", help="Manually set the client id.") + parser_logs.add_argument( + "--serial-port", + help="Manually specify a serial port to use" + "For example /dev/cu.SLAB_USBtoUART.", + ) + + parser_run = subparsers.add_parser( + "run", + help="Validate the configuration, create a binary, " + "upload it, and start MQTT logs.", + ) + parser_run.add_argument( + "--upload-port", + help="Manually specify the upload port/ip to use. " + "For example /dev/cu.SLAB_USBtoUART.", + ) + parser_run.add_argument( + "--no-logs", help="Disable starting MQTT logs.", action="store_true" + ) + parser_run.add_argument( + "--topic", help="Manually set the topic to subscribe to for logs." + ) + parser_run.add_argument( + "--username", help="Manually set the MQTT username for logs." + ) + parser_run.add_argument( + "--password", help="Manually set the MQTT password for logs." + ) + parser_run.add_argument("--client-id", help="Manually set the client id for logs.") + + parser_clean = subparsers.add_parser( + "clean-mqtt", help="Helper to clear an MQTT topic from " "retain messages." + ) + parser_clean.add_argument("--topic", help="Manually set the topic to subscribe to.") + parser_clean.add_argument("--username", help="Manually set the username.") + parser_clean.add_argument("--password", help="Manually set the password.") + parser_clean.add_argument("--client-id", help="Manually set the client id.") + + subparsers.add_parser( + "wizard", + help="A helpful setup wizard that will guide " + "you through setting up esphome.", + ) + + subparsers.add_parser( + "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker." + ) + + subparsers.add_parser("version", help="Print the esphome version and exit.") + + subparsers.add_parser("clean", help="Delete all temporary build files.") + + dashboard = subparsers.add_parser( + "dashboard", help="Create a simple web server for a dashboard." + ) + dashboard.add_argument( + "--port", + help="The HTTP port to open connections on. Defaults to 6052.", + type=int, + default=6052, + ) + dashboard.add_argument( + "--username", + help="The optional username to require " "for authentication.", + type=str, + default="", + ) + dashboard.add_argument( + "--password", + help="The optional password to require " "for authentication.", + type=str, + default="", + ) + dashboard.add_argument( + "--open-ui", help="Open the dashboard UI in a browser.", action="store_true" + ) + dashboard.add_argument("--hassio", help=argparse.SUPPRESS, action="store_true") + dashboard.add_argument( + "--socket", help="Make the dashboard serve under a unix socket", type=str + ) + + vscode = subparsers.add_parser("vscode", help=argparse.SUPPRESS) + vscode.add_argument("--ace", action="store_true") + + subparsers.add_parser("update-all", help=argparse.SUPPRESS) return parser.parse_args(argv[1:]) @@ -512,13 +634,15 @@ def run_esphome(argv): CORE.dashboard = args.dashboard setup_log(args.verbose, args.quiet) - if args.command != 'version' and not args.configuration: + if args.command != "version" and not args.configuration: _LOGGER.error("Missing configuration parameter, see esphome --help.") return 1 if sys.version_info < (3, 6, 0): - _LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible " - "with this Python version. Please reinstall ESPHome with Python 3.6+") + _LOGGER.error( + "You're running ESPHome with Python <3.6. ESPHome is no longer compatible " + "with this Python version. Please reinstall ESPHome with Python 3.6+" + ) return 1 if args.command in PRE_CONFIG_ACTIONS: diff --git a/esphome/api/api_pb2.py b/esphome/api/api_pb2.py index c6c8741f01f1..6262b752c66a 100644 --- a/esphome/api/api_pb2.py +++ b/esphome/api/api_pb2.py @@ -3,93 +3,85 @@ # source: api.proto import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) + +_b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1")) from google.protobuf.internal import enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database + # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() - - DESCRIPTOR = _descriptor.FileDescriptor( - name='api.proto', - package='', - syntax='proto3', - serialized_options=None, - serialized_pb=_b('\n\tapi.proto\"#\n\x0cHelloRequest\x12\x13\n\x0b\x63lient_info\x18\x01 \x01(\t\"Z\n\rHelloResponse\x12\x19\n\x11\x61pi_version_major\x18\x01 \x01(\r\x12\x19\n\x11\x61pi_version_minor\x18\x02 \x01(\r\x12\x13\n\x0bserver_info\x18\x03 \x01(\t\"\"\n\x0e\x43onnectRequest\x12\x10\n\x08password\x18\x01 \x01(\t\"+\n\x0f\x43onnectResponse\x12\x18\n\x10invalid_password\x18\x01 \x01(\x08\"\x13\n\x11\x44isconnectRequest\"\x14\n\x12\x44isconnectResponse\"\r\n\x0bPingRequest\"\x0e\n\x0cPingResponse\"\x13\n\x11\x44\x65viceInfoRequest\"\xad\x01\n\x12\x44\x65viceInfoResponse\x12\x15\n\ruses_password\x18\x01 \x01(\x08\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0bmac_address\x18\x03 \x01(\t\x12\x1c\n\x14\x65sphome_core_version\x18\x04 \x01(\t\x12\x18\n\x10\x63ompilation_time\x18\x05 \x01(\t\x12\r\n\x05model\x18\x06 \x01(\t\x12\x16\n\x0ehas_deep_sleep\x18\x07 \x01(\x08\"\x15\n\x13ListEntitiesRequest\"\x9a\x01\n ListEntitiesBinarySensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x14\n\x0c\x64\x65vice_class\x18\x05 \x01(\t\x12\x1f\n\x17is_status_binary_sensor\x18\x06 \x01(\x08\"s\n\x19ListEntitiesCoverResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x15\n\ris_optimistic\x18\x05 \x01(\x08\"\x90\x01\n\x17ListEntitiesFanResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1c\n\x14supports_oscillation\x18\x05 \x01(\x08\x12\x16\n\x0esupports_speed\x18\x06 \x01(\x08\"\x8a\x02\n\x19ListEntitiesLightResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1b\n\x13supports_brightness\x18\x05 \x01(\x08\x12\x14\n\x0csupports_rgb\x18\x06 \x01(\x08\x12\x1c\n\x14supports_white_value\x18\x07 \x01(\x08\x12\"\n\x1asupports_color_temperature\x18\x08 \x01(\x08\x12\x12\n\nmin_mireds\x18\t \x01(\x02\x12\x12\n\nmax_mireds\x18\n \x01(\x02\x12\x0f\n\x07\x65\x66\x66\x65\x63ts\x18\x0b \x03(\t\"\xa3\x01\n\x1aListEntitiesSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x1b\n\x13unit_of_measurement\x18\x06 \x01(\t\x12\x19\n\x11\x61\x63\x63uracy_decimals\x18\x07 \x01(\x05\"\x7f\n\x1aListEntitiesSwitchResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x12\n\noptimistic\x18\x06 \x01(\x08\"o\n\x1eListEntitiesTextSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\"\x1a\n\x18ListEntitiesDoneResponse\"\x18\n\x16SubscribeStatesRequest\"7\n\x19\x42inarySensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"t\n\x12\x43overStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12-\n\x05state\x18\x02 \x01(\x0e\x32\x1e.CoverStateResponse.CoverState\"\"\n\nCoverState\x12\x08\n\x04OPEN\x10\x00\x12\n\n\x06\x43LOSED\x10\x01\"]\n\x10\x46\x61nStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x13\n\x0boscillating\x18\x03 \x01(\x08\x12\x18\n\x05speed\x18\x04 \x01(\x0e\x32\t.FanSpeed\"\xa8\x01\n\x12LightStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x12\n\nbrightness\x18\x03 \x01(\x02\x12\x0b\n\x03red\x18\x04 \x01(\x02\x12\r\n\x05green\x18\x05 \x01(\x02\x12\x0c\n\x04\x62lue\x18\x06 \x01(\x02\x12\r\n\x05white\x18\x07 \x01(\x02\x12\x19\n\x11\x63olor_temperature\x18\x08 \x01(\x02\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\t \x01(\t\"1\n\x13SensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x02\"1\n\x13SwitchStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"5\n\x17TextSensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\t\"\x98\x01\n\x13\x43overCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\x32\n\x07\x63ommand\x18\x03 \x01(\x0e\x32!.CoverCommandRequest.CoverCommand\"-\n\x0c\x43overCommand\x12\x08\n\x04OPEN\x10\x00\x12\t\n\x05\x43LOSE\x10\x01\x12\x08\n\x04STOP\x10\x02\"\x9d\x01\n\x11\x46\x61nCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x11\n\thas_speed\x18\x04 \x01(\x08\x12\x18\n\x05speed\x18\x05 \x01(\x0e\x32\t.FanSpeed\x12\x17\n\x0fhas_oscillating\x18\x06 \x01(\x08\x12\x13\n\x0boscillating\x18\x07 \x01(\x08\"\x95\x03\n\x13LightCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x16\n\x0ehas_brightness\x18\x04 \x01(\x08\x12\x12\n\nbrightness\x18\x05 \x01(\x02\x12\x0f\n\x07has_rgb\x18\x06 \x01(\x08\x12\x0b\n\x03red\x18\x07 \x01(\x02\x12\r\n\x05green\x18\x08 \x01(\x02\x12\x0c\n\x04\x62lue\x18\t \x01(\x02\x12\x11\n\thas_white\x18\n \x01(\x08\x12\r\n\x05white\x18\x0b \x01(\x02\x12\x1d\n\x15has_color_temperature\x18\x0c \x01(\x08\x12\x19\n\x11\x63olor_temperature\x18\r \x01(\x02\x12\x1d\n\x15has_transition_length\x18\x0e \x01(\x08\x12\x19\n\x11transition_length\x18\x0f \x01(\r\x12\x18\n\x10has_flash_length\x18\x10 \x01(\x08\x12\x14\n\x0c\x66lash_length\x18\x11 \x01(\r\x12\x12\n\nhas_effect\x18\x12 \x01(\x08\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\x13 \x01(\t\"2\n\x14SwitchCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"E\n\x14SubscribeLogsRequest\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x13\n\x0b\x64ump_config\x18\x02 \x01(\x08\"d\n\x15SubscribeLogsResponse\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x0b\n\x03tag\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x13\n\x0bsend_failed\x18\x04 \x01(\x08\"\x1e\n\x1cSubscribeServiceCallsRequest\"\xdf\x02\n\x13ServiceCallResponse\x12\x0f\n\x07service\x18\x01 \x01(\t\x12,\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x1e.ServiceCallResponse.DataEntry\x12=\n\rdata_template\x18\x03 \x03(\x0b\x32&.ServiceCallResponse.DataTemplateEntry\x12\x36\n\tvariables\x18\x04 \x03(\x0b\x32#.ServiceCallResponse.VariablesEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11\x44\x61taTemplateEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x30\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"%\n#SubscribeHomeAssistantStatesRequest\"8\n#SubscribeHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\">\n\x1aHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t\"\x10\n\x0eGetTimeRequest\"(\n\x0fGetTimeResponse\x12\x15\n\repoch_seconds\x18\x01 \x01(\x07*)\n\x08\x46\x61nSpeed\x12\x07\n\x03LOW\x10\x00\x12\n\n\x06MEDIUM\x10\x01\x12\x08\n\x04HIGH\x10\x02*]\n\x08LogLevel\x12\x08\n\x04NONE\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x08\n\x04WARN\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\t\n\x05\x44\x45\x42UG\x10\x04\x12\x0b\n\x07VERBOSE\x10\x05\x12\x10\n\x0cVERY_VERBOSE\x10\x06\x62\x06proto3') + name="api.proto", + package="", + syntax="proto3", + serialized_options=None, + serialized_pb=_b( + '\n\tapi.proto"#\n\x0cHelloRequest\x12\x13\n\x0b\x63lient_info\x18\x01 \x01(\t"Z\n\rHelloResponse\x12\x19\n\x11\x61pi_version_major\x18\x01 \x01(\r\x12\x19\n\x11\x61pi_version_minor\x18\x02 \x01(\r\x12\x13\n\x0bserver_info\x18\x03 \x01(\t""\n\x0e\x43onnectRequest\x12\x10\n\x08password\x18\x01 \x01(\t"+\n\x0f\x43onnectResponse\x12\x18\n\x10invalid_password\x18\x01 \x01(\x08"\x13\n\x11\x44isconnectRequest"\x14\n\x12\x44isconnectResponse"\r\n\x0bPingRequest"\x0e\n\x0cPingResponse"\x13\n\x11\x44\x65viceInfoRequest"\xad\x01\n\x12\x44\x65viceInfoResponse\x12\x15\n\ruses_password\x18\x01 \x01(\x08\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0bmac_address\x18\x03 \x01(\t\x12\x1c\n\x14\x65sphome_core_version\x18\x04 \x01(\t\x12\x18\n\x10\x63ompilation_time\x18\x05 \x01(\t\x12\r\n\x05model\x18\x06 \x01(\t\x12\x16\n\x0ehas_deep_sleep\x18\x07 \x01(\x08"\x15\n\x13ListEntitiesRequest"\x9a\x01\n ListEntitiesBinarySensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x14\n\x0c\x64\x65vice_class\x18\x05 \x01(\t\x12\x1f\n\x17is_status_binary_sensor\x18\x06 \x01(\x08"s\n\x19ListEntitiesCoverResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x15\n\ris_optimistic\x18\x05 \x01(\x08"\x90\x01\n\x17ListEntitiesFanResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1c\n\x14supports_oscillation\x18\x05 \x01(\x08\x12\x16\n\x0esupports_speed\x18\x06 \x01(\x08"\x8a\x02\n\x19ListEntitiesLightResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1b\n\x13supports_brightness\x18\x05 \x01(\x08\x12\x14\n\x0csupports_rgb\x18\x06 \x01(\x08\x12\x1c\n\x14supports_white_value\x18\x07 \x01(\x08\x12"\n\x1asupports_color_temperature\x18\x08 \x01(\x08\x12\x12\n\nmin_mireds\x18\t \x01(\x02\x12\x12\n\nmax_mireds\x18\n \x01(\x02\x12\x0f\n\x07\x65\x66\x66\x65\x63ts\x18\x0b \x03(\t"\xa3\x01\n\x1aListEntitiesSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x1b\n\x13unit_of_measurement\x18\x06 \x01(\t\x12\x19\n\x11\x61\x63\x63uracy_decimals\x18\x07 \x01(\x05"\x7f\n\x1aListEntitiesSwitchResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x12\n\noptimistic\x18\x06 \x01(\x08"o\n\x1eListEntitiesTextSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t"\x1a\n\x18ListEntitiesDoneResponse"\x18\n\x16SubscribeStatesRequest"7\n\x19\x42inarySensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"t\n\x12\x43overStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12-\n\x05state\x18\x02 \x01(\x0e\x32\x1e.CoverStateResponse.CoverState""\n\nCoverState\x12\x08\n\x04OPEN\x10\x00\x12\n\n\x06\x43LOSED\x10\x01"]\n\x10\x46\x61nStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x13\n\x0boscillating\x18\x03 \x01(\x08\x12\x18\n\x05speed\x18\x04 \x01(\x0e\x32\t.FanSpeed"\xa8\x01\n\x12LightStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x12\n\nbrightness\x18\x03 \x01(\x02\x12\x0b\n\x03red\x18\x04 \x01(\x02\x12\r\n\x05green\x18\x05 \x01(\x02\x12\x0c\n\x04\x62lue\x18\x06 \x01(\x02\x12\r\n\x05white\x18\x07 \x01(\x02\x12\x19\n\x11\x63olor_temperature\x18\x08 \x01(\x02\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\t \x01(\t"1\n\x13SensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x02"1\n\x13SwitchStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"5\n\x17TextSensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\t"\x98\x01\n\x13\x43overCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\x32\n\x07\x63ommand\x18\x03 \x01(\x0e\x32!.CoverCommandRequest.CoverCommand"-\n\x0c\x43overCommand\x12\x08\n\x04OPEN\x10\x00\x12\t\n\x05\x43LOSE\x10\x01\x12\x08\n\x04STOP\x10\x02"\x9d\x01\n\x11\x46\x61nCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x11\n\thas_speed\x18\x04 \x01(\x08\x12\x18\n\x05speed\x18\x05 \x01(\x0e\x32\t.FanSpeed\x12\x17\n\x0fhas_oscillating\x18\x06 \x01(\x08\x12\x13\n\x0boscillating\x18\x07 \x01(\x08"\x95\x03\n\x13LightCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x16\n\x0ehas_brightness\x18\x04 \x01(\x08\x12\x12\n\nbrightness\x18\x05 \x01(\x02\x12\x0f\n\x07has_rgb\x18\x06 \x01(\x08\x12\x0b\n\x03red\x18\x07 \x01(\x02\x12\r\n\x05green\x18\x08 \x01(\x02\x12\x0c\n\x04\x62lue\x18\t \x01(\x02\x12\x11\n\thas_white\x18\n \x01(\x08\x12\r\n\x05white\x18\x0b \x01(\x02\x12\x1d\n\x15has_color_temperature\x18\x0c \x01(\x08\x12\x19\n\x11\x63olor_temperature\x18\r \x01(\x02\x12\x1d\n\x15has_transition_length\x18\x0e \x01(\x08\x12\x19\n\x11transition_length\x18\x0f \x01(\r\x12\x18\n\x10has_flash_length\x18\x10 \x01(\x08\x12\x14\n\x0c\x66lash_length\x18\x11 \x01(\r\x12\x12\n\nhas_effect\x18\x12 \x01(\x08\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\x13 \x01(\t"2\n\x14SwitchCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"E\n\x14SubscribeLogsRequest\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x13\n\x0b\x64ump_config\x18\x02 \x01(\x08"d\n\x15SubscribeLogsResponse\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x0b\n\x03tag\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x13\n\x0bsend_failed\x18\x04 \x01(\x08"\x1e\n\x1cSubscribeServiceCallsRequest"\xdf\x02\n\x13ServiceCallResponse\x12\x0f\n\x07service\x18\x01 \x01(\t\x12,\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x1e.ServiceCallResponse.DataEntry\x12=\n\rdata_template\x18\x03 \x03(\x0b\x32&.ServiceCallResponse.DataTemplateEntry\x12\x36\n\tvariables\x18\x04 \x03(\x0b\x32#.ServiceCallResponse.VariablesEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11\x44\x61taTemplateEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x30\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01"%\n#SubscribeHomeAssistantStatesRequest"8\n#SubscribeHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t">\n\x1aHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t"\x10\n\x0eGetTimeRequest"(\n\x0fGetTimeResponse\x12\x15\n\repoch_seconds\x18\x01 \x01(\x07*)\n\x08\x46\x61nSpeed\x12\x07\n\x03LOW\x10\x00\x12\n\n\x06MEDIUM\x10\x01\x12\x08\n\x04HIGH\x10\x02*]\n\x08LogLevel\x12\x08\n\x04NONE\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x08\n\x04WARN\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\t\n\x05\x44\x45\x42UG\x10\x04\x12\x0b\n\x07VERBOSE\x10\x05\x12\x10\n\x0cVERY_VERBOSE\x10\x06\x62\x06proto3' + ), ) _FANSPEED = _descriptor.EnumDescriptor( - name='FanSpeed', - full_name='FanSpeed', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='LOW', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MEDIUM', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='HIGH', index=2, number=2, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=3822, - serialized_end=3863, + name="FanSpeed", + full_name="FanSpeed", + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name="LOW", index=0, number=0, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="MEDIUM", index=1, number=1, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="HIGH", index=2, number=2, serialized_options=None, type=None + ), + ], + containing_type=None, + serialized_options=None, + serialized_start=3822, + serialized_end=3863, ) _sym_db.RegisterEnumDescriptor(_FANSPEED) FanSpeed = enum_type_wrapper.EnumTypeWrapper(_FANSPEED) _LOGLEVEL = _descriptor.EnumDescriptor( - name='LogLevel', - full_name='LogLevel', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='NONE', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='ERROR', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='WARN', index=2, number=2, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='INFO', index=3, number=3, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='DEBUG', index=4, number=4, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='VERBOSE', index=5, number=5, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='VERY_VERBOSE', index=6, number=6, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=3865, - serialized_end=3958, + name="LogLevel", + full_name="LogLevel", + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name="NONE", index=0, number=0, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="ERROR", index=1, number=1, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="WARN", index=2, number=2, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="INFO", index=3, number=3, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="DEBUG", index=4, number=4, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="VERBOSE", index=5, number=5, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="VERY_VERBOSE", index=6, number=6, serialized_options=None, type=None + ), + ], + containing_type=None, + serialized_options=None, + serialized_start=3865, + serialized_end=3958, ) _sym_db.RegisterEnumDescriptor(_LOGLEVEL) @@ -107,2375 +99,3895 @@ _COVERSTATERESPONSE_COVERSTATE = _descriptor.EnumDescriptor( - name='CoverState', - full_name='CoverStateResponse.CoverState', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='OPEN', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='CLOSED', index=1, number=1, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=1808, - serialized_end=1842, + name="CoverState", + full_name="CoverStateResponse.CoverState", + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name="OPEN", index=0, number=0, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="CLOSED", index=1, number=1, serialized_options=None, type=None + ), + ], + containing_type=None, + serialized_options=None, + serialized_start=1808, + serialized_end=1842, ) _sym_db.RegisterEnumDescriptor(_COVERSTATERESPONSE_COVERSTATE) _COVERCOMMANDREQUEST_COVERCOMMAND = _descriptor.EnumDescriptor( - name='CoverCommand', - full_name='CoverCommandRequest.CoverCommand', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='OPEN', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='CLOSE', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STOP', index=2, number=2, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=2375, - serialized_end=2420, + name="CoverCommand", + full_name="CoverCommandRequest.CoverCommand", + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name="OPEN", index=0, number=0, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="CLOSE", index=1, number=1, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="STOP", index=2, number=2, serialized_options=None, type=None + ), + ], + containing_type=None, + serialized_options=None, + serialized_start=2375, + serialized_end=2420, ) _sym_db.RegisterEnumDescriptor(_COVERCOMMANDREQUEST_COVERCOMMAND) _HELLOREQUEST = _descriptor.Descriptor( - name='HelloRequest', - full_name='HelloRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='client_info', full_name='HelloRequest.client_info', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=13, - serialized_end=48, + name="HelloRequest", + full_name="HelloRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="client_info", + full_name="HelloRequest.client_info", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=13, + serialized_end=48, ) _HELLORESPONSE = _descriptor.Descriptor( - name='HelloResponse', - full_name='HelloResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='api_version_major', full_name='HelloResponse.api_version_major', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='api_version_minor', full_name='HelloResponse.api_version_minor', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='server_info', full_name='HelloResponse.server_info', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=50, - serialized_end=140, + name="HelloResponse", + full_name="HelloResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="api_version_major", + full_name="HelloResponse.api_version_major", + index=0, + number=1, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="api_version_minor", + full_name="HelloResponse.api_version_minor", + index=1, + number=2, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="server_info", + full_name="HelloResponse.server_info", + index=2, + number=3, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=50, + serialized_end=140, ) _CONNECTREQUEST = _descriptor.Descriptor( - name='ConnectRequest', - full_name='ConnectRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='password', full_name='ConnectRequest.password', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=142, - serialized_end=176, + name="ConnectRequest", + full_name="ConnectRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="password", + full_name="ConnectRequest.password", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=142, + serialized_end=176, ) _CONNECTRESPONSE = _descriptor.Descriptor( - name='ConnectResponse', - full_name='ConnectResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='invalid_password', full_name='ConnectResponse.invalid_password', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=178, - serialized_end=221, + name="ConnectResponse", + full_name="ConnectResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="invalid_password", + full_name="ConnectResponse.invalid_password", + index=0, + number=1, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=178, + serialized_end=221, ) _DISCONNECTREQUEST = _descriptor.Descriptor( - name='DisconnectRequest', - full_name='DisconnectRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=223, - serialized_end=242, + name="DisconnectRequest", + full_name="DisconnectRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=223, + serialized_end=242, ) _DISCONNECTRESPONSE = _descriptor.Descriptor( - name='DisconnectResponse', - full_name='DisconnectResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=244, - serialized_end=264, + name="DisconnectResponse", + full_name="DisconnectResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=244, + serialized_end=264, ) _PINGREQUEST = _descriptor.Descriptor( - name='PingRequest', - full_name='PingRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=266, - serialized_end=279, + name="PingRequest", + full_name="PingRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=266, + serialized_end=279, ) _PINGRESPONSE = _descriptor.Descriptor( - name='PingResponse', - full_name='PingResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=281, - serialized_end=295, + name="PingResponse", + full_name="PingResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=281, + serialized_end=295, ) _DEVICEINFOREQUEST = _descriptor.Descriptor( - name='DeviceInfoRequest', - full_name='DeviceInfoRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=297, - serialized_end=316, + name="DeviceInfoRequest", + full_name="DeviceInfoRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=297, + serialized_end=316, ) _DEVICEINFORESPONSE = _descriptor.Descriptor( - name='DeviceInfoResponse', - full_name='DeviceInfoResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='uses_password', full_name='DeviceInfoResponse.uses_password', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='DeviceInfoResponse.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='mac_address', full_name='DeviceInfoResponse.mac_address', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='esphome_core_version', full_name='DeviceInfoResponse.esphome_core_version', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='compilation_time', full_name='DeviceInfoResponse.compilation_time', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='model', full_name='DeviceInfoResponse.model', index=5, - number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_deep_sleep', full_name='DeviceInfoResponse.has_deep_sleep', index=6, - number=7, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=319, - serialized_end=492, + name="DeviceInfoResponse", + full_name="DeviceInfoResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="uses_password", + full_name="DeviceInfoResponse.uses_password", + index=0, + number=1, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="name", + full_name="DeviceInfoResponse.name", + index=1, + number=2, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="mac_address", + full_name="DeviceInfoResponse.mac_address", + index=2, + number=3, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="esphome_core_version", + full_name="DeviceInfoResponse.esphome_core_version", + index=3, + number=4, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="compilation_time", + full_name="DeviceInfoResponse.compilation_time", + index=4, + number=5, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="model", + full_name="DeviceInfoResponse.model", + index=5, + number=6, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_deep_sleep", + full_name="DeviceInfoResponse.has_deep_sleep", + index=6, + number=7, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=319, + serialized_end=492, ) _LISTENTITIESREQUEST = _descriptor.Descriptor( - name='ListEntitiesRequest', - full_name='ListEntitiesRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=494, - serialized_end=515, + name="ListEntitiesRequest", + full_name="ListEntitiesRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=494, + serialized_end=515, ) _LISTENTITIESBINARYSENSORRESPONSE = _descriptor.Descriptor( - name='ListEntitiesBinarySensorResponse', - full_name='ListEntitiesBinarySensorResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='object_id', full_name='ListEntitiesBinarySensorResponse.object_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='key', full_name='ListEntitiesBinarySensorResponse.key', index=1, - number=2, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='ListEntitiesBinarySensorResponse.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='unique_id', full_name='ListEntitiesBinarySensorResponse.unique_id', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='device_class', full_name='ListEntitiesBinarySensorResponse.device_class', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='is_status_binary_sensor', full_name='ListEntitiesBinarySensorResponse.is_status_binary_sensor', index=5, - number=6, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=518, - serialized_end=672, + name="ListEntitiesBinarySensorResponse", + full_name="ListEntitiesBinarySensorResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="object_id", + full_name="ListEntitiesBinarySensorResponse.object_id", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="key", + full_name="ListEntitiesBinarySensorResponse.key", + index=1, + number=2, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="name", + full_name="ListEntitiesBinarySensorResponse.name", + index=2, + number=3, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="unique_id", + full_name="ListEntitiesBinarySensorResponse.unique_id", + index=3, + number=4, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="device_class", + full_name="ListEntitiesBinarySensorResponse.device_class", + index=4, + number=5, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="is_status_binary_sensor", + full_name="ListEntitiesBinarySensorResponse.is_status_binary_sensor", + index=5, + number=6, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=518, + serialized_end=672, ) _LISTENTITIESCOVERRESPONSE = _descriptor.Descriptor( - name='ListEntitiesCoverResponse', - full_name='ListEntitiesCoverResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='object_id', full_name='ListEntitiesCoverResponse.object_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='key', full_name='ListEntitiesCoverResponse.key', index=1, - number=2, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='ListEntitiesCoverResponse.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='unique_id', full_name='ListEntitiesCoverResponse.unique_id', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='is_optimistic', full_name='ListEntitiesCoverResponse.is_optimistic', index=4, - number=5, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=674, - serialized_end=789, + name="ListEntitiesCoverResponse", + full_name="ListEntitiesCoverResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="object_id", + full_name="ListEntitiesCoverResponse.object_id", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="key", + full_name="ListEntitiesCoverResponse.key", + index=1, + number=2, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="name", + full_name="ListEntitiesCoverResponse.name", + index=2, + number=3, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="unique_id", + full_name="ListEntitiesCoverResponse.unique_id", + index=3, + number=4, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="is_optimistic", + full_name="ListEntitiesCoverResponse.is_optimistic", + index=4, + number=5, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=674, + serialized_end=789, ) _LISTENTITIESFANRESPONSE = _descriptor.Descriptor( - name='ListEntitiesFanResponse', - full_name='ListEntitiesFanResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='object_id', full_name='ListEntitiesFanResponse.object_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='key', full_name='ListEntitiesFanResponse.key', index=1, - number=2, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='ListEntitiesFanResponse.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='unique_id', full_name='ListEntitiesFanResponse.unique_id', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='supports_oscillation', full_name='ListEntitiesFanResponse.supports_oscillation', index=4, - number=5, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='supports_speed', full_name='ListEntitiesFanResponse.supports_speed', index=5, - number=6, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=792, - serialized_end=936, + name="ListEntitiesFanResponse", + full_name="ListEntitiesFanResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="object_id", + full_name="ListEntitiesFanResponse.object_id", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="key", + full_name="ListEntitiesFanResponse.key", + index=1, + number=2, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="name", + full_name="ListEntitiesFanResponse.name", + index=2, + number=3, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="unique_id", + full_name="ListEntitiesFanResponse.unique_id", + index=3, + number=4, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="supports_oscillation", + full_name="ListEntitiesFanResponse.supports_oscillation", + index=4, + number=5, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="supports_speed", + full_name="ListEntitiesFanResponse.supports_speed", + index=5, + number=6, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=792, + serialized_end=936, ) _LISTENTITIESLIGHTRESPONSE = _descriptor.Descriptor( - name='ListEntitiesLightResponse', - full_name='ListEntitiesLightResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='object_id', full_name='ListEntitiesLightResponse.object_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='key', full_name='ListEntitiesLightResponse.key', index=1, - number=2, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='ListEntitiesLightResponse.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='unique_id', full_name='ListEntitiesLightResponse.unique_id', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='supports_brightness', full_name='ListEntitiesLightResponse.supports_brightness', index=4, - number=5, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='supports_rgb', full_name='ListEntitiesLightResponse.supports_rgb', index=5, - number=6, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='supports_white_value', full_name='ListEntitiesLightResponse.supports_white_value', index=6, - number=7, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='supports_color_temperature', full_name='ListEntitiesLightResponse.supports_color_temperature', index=7, - number=8, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='min_mireds', full_name='ListEntitiesLightResponse.min_mireds', index=8, - number=9, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='max_mireds', full_name='ListEntitiesLightResponse.max_mireds', index=9, - number=10, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='effects', full_name='ListEntitiesLightResponse.effects', index=10, - number=11, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=939, - serialized_end=1205, + name="ListEntitiesLightResponse", + full_name="ListEntitiesLightResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="object_id", + full_name="ListEntitiesLightResponse.object_id", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="key", + full_name="ListEntitiesLightResponse.key", + index=1, + number=2, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="name", + full_name="ListEntitiesLightResponse.name", + index=2, + number=3, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="unique_id", + full_name="ListEntitiesLightResponse.unique_id", + index=3, + number=4, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="supports_brightness", + full_name="ListEntitiesLightResponse.supports_brightness", + index=4, + number=5, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="supports_rgb", + full_name="ListEntitiesLightResponse.supports_rgb", + index=5, + number=6, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="supports_white_value", + full_name="ListEntitiesLightResponse.supports_white_value", + index=6, + number=7, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="supports_color_temperature", + full_name="ListEntitiesLightResponse.supports_color_temperature", + index=7, + number=8, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="min_mireds", + full_name="ListEntitiesLightResponse.min_mireds", + index=8, + number=9, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="max_mireds", + full_name="ListEntitiesLightResponse.max_mireds", + index=9, + number=10, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="effects", + full_name="ListEntitiesLightResponse.effects", + index=10, + number=11, + type=9, + cpp_type=9, + label=3, + has_default_value=False, + default_value=[], + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=939, + serialized_end=1205, ) _LISTENTITIESSENSORRESPONSE = _descriptor.Descriptor( - name='ListEntitiesSensorResponse', - full_name='ListEntitiesSensorResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='object_id', full_name='ListEntitiesSensorResponse.object_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='key', full_name='ListEntitiesSensorResponse.key', index=1, - number=2, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='ListEntitiesSensorResponse.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='unique_id', full_name='ListEntitiesSensorResponse.unique_id', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='icon', full_name='ListEntitiesSensorResponse.icon', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='unit_of_measurement', full_name='ListEntitiesSensorResponse.unit_of_measurement', index=5, - number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='accuracy_decimals', full_name='ListEntitiesSensorResponse.accuracy_decimals', index=6, - number=7, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1208, - serialized_end=1371, + name="ListEntitiesSensorResponse", + full_name="ListEntitiesSensorResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="object_id", + full_name="ListEntitiesSensorResponse.object_id", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="key", + full_name="ListEntitiesSensorResponse.key", + index=1, + number=2, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="name", + full_name="ListEntitiesSensorResponse.name", + index=2, + number=3, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="unique_id", + full_name="ListEntitiesSensorResponse.unique_id", + index=3, + number=4, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="icon", + full_name="ListEntitiesSensorResponse.icon", + index=4, + number=5, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="unit_of_measurement", + full_name="ListEntitiesSensorResponse.unit_of_measurement", + index=5, + number=6, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="accuracy_decimals", + full_name="ListEntitiesSensorResponse.accuracy_decimals", + index=6, + number=7, + type=5, + cpp_type=1, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1208, + serialized_end=1371, ) _LISTENTITIESSWITCHRESPONSE = _descriptor.Descriptor( - name='ListEntitiesSwitchResponse', - full_name='ListEntitiesSwitchResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='object_id', full_name='ListEntitiesSwitchResponse.object_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='key', full_name='ListEntitiesSwitchResponse.key', index=1, - number=2, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='ListEntitiesSwitchResponse.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='unique_id', full_name='ListEntitiesSwitchResponse.unique_id', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='icon', full_name='ListEntitiesSwitchResponse.icon', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='optimistic', full_name='ListEntitiesSwitchResponse.optimistic', index=5, - number=6, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1373, - serialized_end=1500, + name="ListEntitiesSwitchResponse", + full_name="ListEntitiesSwitchResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="object_id", + full_name="ListEntitiesSwitchResponse.object_id", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="key", + full_name="ListEntitiesSwitchResponse.key", + index=1, + number=2, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="name", + full_name="ListEntitiesSwitchResponse.name", + index=2, + number=3, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="unique_id", + full_name="ListEntitiesSwitchResponse.unique_id", + index=3, + number=4, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="icon", + full_name="ListEntitiesSwitchResponse.icon", + index=4, + number=5, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="optimistic", + full_name="ListEntitiesSwitchResponse.optimistic", + index=5, + number=6, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1373, + serialized_end=1500, ) _LISTENTITIESTEXTSENSORRESPONSE = _descriptor.Descriptor( - name='ListEntitiesTextSensorResponse', - full_name='ListEntitiesTextSensorResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='object_id', full_name='ListEntitiesTextSensorResponse.object_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='key', full_name='ListEntitiesTextSensorResponse.key', index=1, - number=2, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='ListEntitiesTextSensorResponse.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='unique_id', full_name='ListEntitiesTextSensorResponse.unique_id', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='icon', full_name='ListEntitiesTextSensorResponse.icon', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1502, - serialized_end=1613, + name="ListEntitiesTextSensorResponse", + full_name="ListEntitiesTextSensorResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="object_id", + full_name="ListEntitiesTextSensorResponse.object_id", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="key", + full_name="ListEntitiesTextSensorResponse.key", + index=1, + number=2, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="name", + full_name="ListEntitiesTextSensorResponse.name", + index=2, + number=3, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="unique_id", + full_name="ListEntitiesTextSensorResponse.unique_id", + index=3, + number=4, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="icon", + full_name="ListEntitiesTextSensorResponse.icon", + index=4, + number=5, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1502, + serialized_end=1613, ) _LISTENTITIESDONERESPONSE = _descriptor.Descriptor( - name='ListEntitiesDoneResponse', - full_name='ListEntitiesDoneResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1615, - serialized_end=1641, + name="ListEntitiesDoneResponse", + full_name="ListEntitiesDoneResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1615, + serialized_end=1641, ) _SUBSCRIBESTATESREQUEST = _descriptor.Descriptor( - name='SubscribeStatesRequest', - full_name='SubscribeStatesRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1643, - serialized_end=1667, + name="SubscribeStatesRequest", + full_name="SubscribeStatesRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1643, + serialized_end=1667, ) _BINARYSENSORSTATERESPONSE = _descriptor.Descriptor( - name='BinarySensorStateResponse', - full_name='BinarySensorStateResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='BinarySensorStateResponse.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='BinarySensorStateResponse.state', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1669, - serialized_end=1724, + name="BinarySensorStateResponse", + full_name="BinarySensorStateResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="BinarySensorStateResponse.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="BinarySensorStateResponse.state", + index=1, + number=2, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1669, + serialized_end=1724, ) _COVERSTATERESPONSE = _descriptor.Descriptor( - name='CoverStateResponse', - full_name='CoverStateResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='CoverStateResponse.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='CoverStateResponse.state', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _COVERSTATERESPONSE_COVERSTATE, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1726, - serialized_end=1842, + name="CoverStateResponse", + full_name="CoverStateResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="CoverStateResponse.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="CoverStateResponse.state", + index=1, + number=2, + type=14, + cpp_type=8, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[ + _COVERSTATERESPONSE_COVERSTATE, + ], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1726, + serialized_end=1842, ) _FANSTATERESPONSE = _descriptor.Descriptor( - name='FanStateResponse', - full_name='FanStateResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='FanStateResponse.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='FanStateResponse.state', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='oscillating', full_name='FanStateResponse.oscillating', index=2, - number=3, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='speed', full_name='FanStateResponse.speed', index=3, - number=4, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1844, - serialized_end=1937, + name="FanStateResponse", + full_name="FanStateResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="FanStateResponse.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="FanStateResponse.state", + index=1, + number=2, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="oscillating", + full_name="FanStateResponse.oscillating", + index=2, + number=3, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="speed", + full_name="FanStateResponse.speed", + index=3, + number=4, + type=14, + cpp_type=8, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1844, + serialized_end=1937, ) _LIGHTSTATERESPONSE = _descriptor.Descriptor( - name='LightStateResponse', - full_name='LightStateResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='LightStateResponse.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='LightStateResponse.state', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='brightness', full_name='LightStateResponse.brightness', index=2, - number=3, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='red', full_name='LightStateResponse.red', index=3, - number=4, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='green', full_name='LightStateResponse.green', index=4, - number=5, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='blue', full_name='LightStateResponse.blue', index=5, - number=6, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='white', full_name='LightStateResponse.white', index=6, - number=7, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='color_temperature', full_name='LightStateResponse.color_temperature', index=7, - number=8, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='effect', full_name='LightStateResponse.effect', index=8, - number=9, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1940, - serialized_end=2108, + name="LightStateResponse", + full_name="LightStateResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="LightStateResponse.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="LightStateResponse.state", + index=1, + number=2, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="brightness", + full_name="LightStateResponse.brightness", + index=2, + number=3, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="red", + full_name="LightStateResponse.red", + index=3, + number=4, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="green", + full_name="LightStateResponse.green", + index=4, + number=5, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="blue", + full_name="LightStateResponse.blue", + index=5, + number=6, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="white", + full_name="LightStateResponse.white", + index=6, + number=7, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="color_temperature", + full_name="LightStateResponse.color_temperature", + index=7, + number=8, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="effect", + full_name="LightStateResponse.effect", + index=8, + number=9, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1940, + serialized_end=2108, ) _SENSORSTATERESPONSE = _descriptor.Descriptor( - name='SensorStateResponse', - full_name='SensorStateResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='SensorStateResponse.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='SensorStateResponse.state', index=1, - number=2, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2110, - serialized_end=2159, + name="SensorStateResponse", + full_name="SensorStateResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="SensorStateResponse.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="SensorStateResponse.state", + index=1, + number=2, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=2110, + serialized_end=2159, ) _SWITCHSTATERESPONSE = _descriptor.Descriptor( - name='SwitchStateResponse', - full_name='SwitchStateResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='SwitchStateResponse.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='SwitchStateResponse.state', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2161, - serialized_end=2210, + name="SwitchStateResponse", + full_name="SwitchStateResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="SwitchStateResponse.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="SwitchStateResponse.state", + index=1, + number=2, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=2161, + serialized_end=2210, ) _TEXTSENSORSTATERESPONSE = _descriptor.Descriptor( - name='TextSensorStateResponse', - full_name='TextSensorStateResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='TextSensorStateResponse.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='TextSensorStateResponse.state', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2212, - serialized_end=2265, + name="TextSensorStateResponse", + full_name="TextSensorStateResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="TextSensorStateResponse.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="TextSensorStateResponse.state", + index=1, + number=2, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=2212, + serialized_end=2265, ) _COVERCOMMANDREQUEST = _descriptor.Descriptor( - name='CoverCommandRequest', - full_name='CoverCommandRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='CoverCommandRequest.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_state', full_name='CoverCommandRequest.has_state', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='command', full_name='CoverCommandRequest.command', index=2, - number=3, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _COVERCOMMANDREQUEST_COVERCOMMAND, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2268, - serialized_end=2420, + name="CoverCommandRequest", + full_name="CoverCommandRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="CoverCommandRequest.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_state", + full_name="CoverCommandRequest.has_state", + index=1, + number=2, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="command", + full_name="CoverCommandRequest.command", + index=2, + number=3, + type=14, + cpp_type=8, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[ + _COVERCOMMANDREQUEST_COVERCOMMAND, + ], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=2268, + serialized_end=2420, ) _FANCOMMANDREQUEST = _descriptor.Descriptor( - name='FanCommandRequest', - full_name='FanCommandRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='FanCommandRequest.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_state', full_name='FanCommandRequest.has_state', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='FanCommandRequest.state', index=2, - number=3, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_speed', full_name='FanCommandRequest.has_speed', index=3, - number=4, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='speed', full_name='FanCommandRequest.speed', index=4, - number=5, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_oscillating', full_name='FanCommandRequest.has_oscillating', index=5, - number=6, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='oscillating', full_name='FanCommandRequest.oscillating', index=6, - number=7, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2423, - serialized_end=2580, + name="FanCommandRequest", + full_name="FanCommandRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="FanCommandRequest.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_state", + full_name="FanCommandRequest.has_state", + index=1, + number=2, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="FanCommandRequest.state", + index=2, + number=3, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_speed", + full_name="FanCommandRequest.has_speed", + index=3, + number=4, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="speed", + full_name="FanCommandRequest.speed", + index=4, + number=5, + type=14, + cpp_type=8, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_oscillating", + full_name="FanCommandRequest.has_oscillating", + index=5, + number=6, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="oscillating", + full_name="FanCommandRequest.oscillating", + index=6, + number=7, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=2423, + serialized_end=2580, ) _LIGHTCOMMANDREQUEST = _descriptor.Descriptor( - name='LightCommandRequest', - full_name='LightCommandRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='LightCommandRequest.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_state', full_name='LightCommandRequest.has_state', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='LightCommandRequest.state', index=2, - number=3, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_brightness', full_name='LightCommandRequest.has_brightness', index=3, - number=4, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='brightness', full_name='LightCommandRequest.brightness', index=4, - number=5, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_rgb', full_name='LightCommandRequest.has_rgb', index=5, - number=6, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='red', full_name='LightCommandRequest.red', index=6, - number=7, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='green', full_name='LightCommandRequest.green', index=7, - number=8, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='blue', full_name='LightCommandRequest.blue', index=8, - number=9, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_white', full_name='LightCommandRequest.has_white', index=9, - number=10, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='white', full_name='LightCommandRequest.white', index=10, - number=11, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_color_temperature', full_name='LightCommandRequest.has_color_temperature', index=11, - number=12, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='color_temperature', full_name='LightCommandRequest.color_temperature', index=12, - number=13, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_transition_length', full_name='LightCommandRequest.has_transition_length', index=13, - number=14, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='transition_length', full_name='LightCommandRequest.transition_length', index=14, - number=15, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_flash_length', full_name='LightCommandRequest.has_flash_length', index=15, - number=16, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='flash_length', full_name='LightCommandRequest.flash_length', index=16, - number=17, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='has_effect', full_name='LightCommandRequest.has_effect', index=17, - number=18, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='effect', full_name='LightCommandRequest.effect', index=18, - number=19, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2583, - serialized_end=2988, + name="LightCommandRequest", + full_name="LightCommandRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="LightCommandRequest.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_state", + full_name="LightCommandRequest.has_state", + index=1, + number=2, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="LightCommandRequest.state", + index=2, + number=3, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_brightness", + full_name="LightCommandRequest.has_brightness", + index=3, + number=4, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="brightness", + full_name="LightCommandRequest.brightness", + index=4, + number=5, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_rgb", + full_name="LightCommandRequest.has_rgb", + index=5, + number=6, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="red", + full_name="LightCommandRequest.red", + index=6, + number=7, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="green", + full_name="LightCommandRequest.green", + index=7, + number=8, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="blue", + full_name="LightCommandRequest.blue", + index=8, + number=9, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_white", + full_name="LightCommandRequest.has_white", + index=9, + number=10, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="white", + full_name="LightCommandRequest.white", + index=10, + number=11, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_color_temperature", + full_name="LightCommandRequest.has_color_temperature", + index=11, + number=12, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="color_temperature", + full_name="LightCommandRequest.color_temperature", + index=12, + number=13, + type=2, + cpp_type=6, + label=1, + has_default_value=False, + default_value=float(0), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_transition_length", + full_name="LightCommandRequest.has_transition_length", + index=13, + number=14, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="transition_length", + full_name="LightCommandRequest.transition_length", + index=14, + number=15, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_flash_length", + full_name="LightCommandRequest.has_flash_length", + index=15, + number=16, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="flash_length", + full_name="LightCommandRequest.flash_length", + index=16, + number=17, + type=13, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="has_effect", + full_name="LightCommandRequest.has_effect", + index=17, + number=18, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="effect", + full_name="LightCommandRequest.effect", + index=18, + number=19, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=2583, + serialized_end=2988, ) _SWITCHCOMMANDREQUEST = _descriptor.Descriptor( - name='SwitchCommandRequest', - full_name='SwitchCommandRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='SwitchCommandRequest.key', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='SwitchCommandRequest.state', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2990, - serialized_end=3040, + name="SwitchCommandRequest", + full_name="SwitchCommandRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="SwitchCommandRequest.key", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="SwitchCommandRequest.state", + index=1, + number=2, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=2990, + serialized_end=3040, ) _SUBSCRIBELOGSREQUEST = _descriptor.Descriptor( - name='SubscribeLogsRequest', - full_name='SubscribeLogsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='level', full_name='SubscribeLogsRequest.level', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='dump_config', full_name='SubscribeLogsRequest.dump_config', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3042, - serialized_end=3111, + name="SubscribeLogsRequest", + full_name="SubscribeLogsRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="level", + full_name="SubscribeLogsRequest.level", + index=0, + number=1, + type=14, + cpp_type=8, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="dump_config", + full_name="SubscribeLogsRequest.dump_config", + index=1, + number=2, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3042, + serialized_end=3111, ) _SUBSCRIBELOGSRESPONSE = _descriptor.Descriptor( - name='SubscribeLogsResponse', - full_name='SubscribeLogsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='level', full_name='SubscribeLogsResponse.level', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='tag', full_name='SubscribeLogsResponse.tag', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='message', full_name='SubscribeLogsResponse.message', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='send_failed', full_name='SubscribeLogsResponse.send_failed', index=3, - number=4, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3113, - serialized_end=3213, + name="SubscribeLogsResponse", + full_name="SubscribeLogsResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="level", + full_name="SubscribeLogsResponse.level", + index=0, + number=1, + type=14, + cpp_type=8, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="tag", + full_name="SubscribeLogsResponse.tag", + index=1, + number=2, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="message", + full_name="SubscribeLogsResponse.message", + index=2, + number=3, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="send_failed", + full_name="SubscribeLogsResponse.send_failed", + index=3, + number=4, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3113, + serialized_end=3213, ) _SUBSCRIBESERVICECALLSREQUEST = _descriptor.Descriptor( - name='SubscribeServiceCallsRequest', - full_name='SubscribeServiceCallsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3215, - serialized_end=3245, + name="SubscribeServiceCallsRequest", + full_name="SubscribeServiceCallsRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3215, + serialized_end=3245, ) _SERVICECALLRESPONSE_DATAENTRY = _descriptor.Descriptor( - name='DataEntry', - full_name='ServiceCallResponse.DataEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='ServiceCallResponse.DataEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='ServiceCallResponse.DataEntry.value', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3453, - serialized_end=3496, + name="DataEntry", + full_name="ServiceCallResponse.DataEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="ServiceCallResponse.DataEntry.key", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="ServiceCallResponse.DataEntry.value", + index=1, + number=2, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=_b("8\001"), + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3453, + serialized_end=3496, ) _SERVICECALLRESPONSE_DATATEMPLATEENTRY = _descriptor.Descriptor( - name='DataTemplateEntry', - full_name='ServiceCallResponse.DataTemplateEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='ServiceCallResponse.DataTemplateEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='ServiceCallResponse.DataTemplateEntry.value', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3498, - serialized_end=3549, + name="DataTemplateEntry", + full_name="ServiceCallResponse.DataTemplateEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="ServiceCallResponse.DataTemplateEntry.key", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="ServiceCallResponse.DataTemplateEntry.value", + index=1, + number=2, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=_b("8\001"), + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3498, + serialized_end=3549, ) _SERVICECALLRESPONSE_VARIABLESENTRY = _descriptor.Descriptor( - name='VariablesEntry', - full_name='ServiceCallResponse.VariablesEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='ServiceCallResponse.VariablesEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='ServiceCallResponse.VariablesEntry.value', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3551, - serialized_end=3599, + name="VariablesEntry", + full_name="ServiceCallResponse.VariablesEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="ServiceCallResponse.VariablesEntry.key", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="ServiceCallResponse.VariablesEntry.value", + index=1, + number=2, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=_b("8\001"), + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3551, + serialized_end=3599, ) _SERVICECALLRESPONSE = _descriptor.Descriptor( - name='ServiceCallResponse', - full_name='ServiceCallResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='service', full_name='ServiceCallResponse.service', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='data', full_name='ServiceCallResponse.data', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='data_template', full_name='ServiceCallResponse.data_template', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='variables', full_name='ServiceCallResponse.variables', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_SERVICECALLRESPONSE_DATAENTRY, _SERVICECALLRESPONSE_DATATEMPLATEENTRY, _SERVICECALLRESPONSE_VARIABLESENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3248, - serialized_end=3599, + name="ServiceCallResponse", + full_name="ServiceCallResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="service", + full_name="ServiceCallResponse.service", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="data", + full_name="ServiceCallResponse.data", + index=1, + number=2, + type=11, + cpp_type=10, + label=3, + has_default_value=False, + default_value=[], + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="data_template", + full_name="ServiceCallResponse.data_template", + index=2, + number=3, + type=11, + cpp_type=10, + label=3, + has_default_value=False, + default_value=[], + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="variables", + full_name="ServiceCallResponse.variables", + index=3, + number=4, + type=11, + cpp_type=10, + label=3, + has_default_value=False, + default_value=[], + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[ + _SERVICECALLRESPONSE_DATAENTRY, + _SERVICECALLRESPONSE_DATATEMPLATEENTRY, + _SERVICECALLRESPONSE_VARIABLESENTRY, + ], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3248, + serialized_end=3599, ) _SUBSCRIBEHOMEASSISTANTSTATESREQUEST = _descriptor.Descriptor( - name='SubscribeHomeAssistantStatesRequest', - full_name='SubscribeHomeAssistantStatesRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3601, - serialized_end=3638, + name="SubscribeHomeAssistantStatesRequest", + full_name="SubscribeHomeAssistantStatesRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3601, + serialized_end=3638, ) _SUBSCRIBEHOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( - name='SubscribeHomeAssistantStateResponse', - full_name='SubscribeHomeAssistantStateResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='entity_id', full_name='SubscribeHomeAssistantStateResponse.entity_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3640, - serialized_end=3696, + name="SubscribeHomeAssistantStateResponse", + full_name="SubscribeHomeAssistantStateResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="entity_id", + full_name="SubscribeHomeAssistantStateResponse.entity_id", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3640, + serialized_end=3696, ) _HOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( - name='HomeAssistantStateResponse', - full_name='HomeAssistantStateResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='entity_id', full_name='HomeAssistantStateResponse.entity_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='HomeAssistantStateResponse.state', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3698, - serialized_end=3760, + name="HomeAssistantStateResponse", + full_name="HomeAssistantStateResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="entity_id", + full_name="HomeAssistantStateResponse.entity_id", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="state", + full_name="HomeAssistantStateResponse.state", + index=1, + number=2, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3698, + serialized_end=3760, ) _GETTIMEREQUEST = _descriptor.Descriptor( - name='GetTimeRequest', - full_name='GetTimeRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3762, - serialized_end=3778, + name="GetTimeRequest", + full_name="GetTimeRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3762, + serialized_end=3778, ) _GETTIMERESPONSE = _descriptor.Descriptor( - name='GetTimeResponse', - full_name='GetTimeResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='epoch_seconds', full_name='GetTimeResponse.epoch_seconds', index=0, - number=1, type=7, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3780, - serialized_end=3820, -) - -_COVERSTATERESPONSE.fields_by_name['state'].enum_type = _COVERSTATERESPONSE_COVERSTATE + name="GetTimeResponse", + full_name="GetTimeResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="epoch_seconds", + full_name="GetTimeResponse.epoch_seconds", + index=0, + number=1, + type=7, + cpp_type=3, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3780, + serialized_end=3820, +) + +_COVERSTATERESPONSE.fields_by_name["state"].enum_type = _COVERSTATERESPONSE_COVERSTATE _COVERSTATERESPONSE_COVERSTATE.containing_type = _COVERSTATERESPONSE -_FANSTATERESPONSE.fields_by_name['speed'].enum_type = _FANSPEED -_COVERCOMMANDREQUEST.fields_by_name['command'].enum_type = _COVERCOMMANDREQUEST_COVERCOMMAND +_FANSTATERESPONSE.fields_by_name["speed"].enum_type = _FANSPEED +_COVERCOMMANDREQUEST.fields_by_name[ + "command" +].enum_type = _COVERCOMMANDREQUEST_COVERCOMMAND _COVERCOMMANDREQUEST_COVERCOMMAND.containing_type = _COVERCOMMANDREQUEST -_FANCOMMANDREQUEST.fields_by_name['speed'].enum_type = _FANSPEED -_SUBSCRIBELOGSREQUEST.fields_by_name['level'].enum_type = _LOGLEVEL -_SUBSCRIBELOGSRESPONSE.fields_by_name['level'].enum_type = _LOGLEVEL +_FANCOMMANDREQUEST.fields_by_name["speed"].enum_type = _FANSPEED +_SUBSCRIBELOGSREQUEST.fields_by_name["level"].enum_type = _LOGLEVEL +_SUBSCRIBELOGSRESPONSE.fields_by_name["level"].enum_type = _LOGLEVEL _SERVICECALLRESPONSE_DATAENTRY.containing_type = _SERVICECALLRESPONSE _SERVICECALLRESPONSE_DATATEMPLATEENTRY.containing_type = _SERVICECALLRESPONSE _SERVICECALLRESPONSE_VARIABLESENTRY.containing_type = _SERVICECALLRESPONSE -_SERVICECALLRESPONSE.fields_by_name['data'].message_type = _SERVICECALLRESPONSE_DATAENTRY -_SERVICECALLRESPONSE.fields_by_name['data_template'].message_type = _SERVICECALLRESPONSE_DATATEMPLATEENTRY -_SERVICECALLRESPONSE.fields_by_name['variables'].message_type = _SERVICECALLRESPONSE_VARIABLESENTRY -DESCRIPTOR.message_types_by_name['HelloRequest'] = _HELLOREQUEST -DESCRIPTOR.message_types_by_name['HelloResponse'] = _HELLORESPONSE -DESCRIPTOR.message_types_by_name['ConnectRequest'] = _CONNECTREQUEST -DESCRIPTOR.message_types_by_name['ConnectResponse'] = _CONNECTRESPONSE -DESCRIPTOR.message_types_by_name['DisconnectRequest'] = _DISCONNECTREQUEST -DESCRIPTOR.message_types_by_name['DisconnectResponse'] = _DISCONNECTRESPONSE -DESCRIPTOR.message_types_by_name['PingRequest'] = _PINGREQUEST -DESCRIPTOR.message_types_by_name['PingResponse'] = _PINGRESPONSE -DESCRIPTOR.message_types_by_name['DeviceInfoRequest'] = _DEVICEINFOREQUEST -DESCRIPTOR.message_types_by_name['DeviceInfoResponse'] = _DEVICEINFORESPONSE -DESCRIPTOR.message_types_by_name['ListEntitiesRequest'] = _LISTENTITIESREQUEST -DESCRIPTOR.message_types_by_name['ListEntitiesBinarySensorResponse'] = _LISTENTITIESBINARYSENSORRESPONSE -DESCRIPTOR.message_types_by_name['ListEntitiesCoverResponse'] = _LISTENTITIESCOVERRESPONSE -DESCRIPTOR.message_types_by_name['ListEntitiesFanResponse'] = _LISTENTITIESFANRESPONSE -DESCRIPTOR.message_types_by_name['ListEntitiesLightResponse'] = _LISTENTITIESLIGHTRESPONSE -DESCRIPTOR.message_types_by_name['ListEntitiesSensorResponse'] = _LISTENTITIESSENSORRESPONSE -DESCRIPTOR.message_types_by_name['ListEntitiesSwitchResponse'] = _LISTENTITIESSWITCHRESPONSE -DESCRIPTOR.message_types_by_name['ListEntitiesTextSensorResponse'] = _LISTENTITIESTEXTSENSORRESPONSE -DESCRIPTOR.message_types_by_name['ListEntitiesDoneResponse'] = _LISTENTITIESDONERESPONSE -DESCRIPTOR.message_types_by_name['SubscribeStatesRequest'] = _SUBSCRIBESTATESREQUEST -DESCRIPTOR.message_types_by_name['BinarySensorStateResponse'] = _BINARYSENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name['CoverStateResponse'] = _COVERSTATERESPONSE -DESCRIPTOR.message_types_by_name['FanStateResponse'] = _FANSTATERESPONSE -DESCRIPTOR.message_types_by_name['LightStateResponse'] = _LIGHTSTATERESPONSE -DESCRIPTOR.message_types_by_name['SensorStateResponse'] = _SENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name['SwitchStateResponse'] = _SWITCHSTATERESPONSE -DESCRIPTOR.message_types_by_name['TextSensorStateResponse'] = _TEXTSENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name['CoverCommandRequest'] = _COVERCOMMANDREQUEST -DESCRIPTOR.message_types_by_name['FanCommandRequest'] = _FANCOMMANDREQUEST -DESCRIPTOR.message_types_by_name['LightCommandRequest'] = _LIGHTCOMMANDREQUEST -DESCRIPTOR.message_types_by_name['SwitchCommandRequest'] = _SWITCHCOMMANDREQUEST -DESCRIPTOR.message_types_by_name['SubscribeLogsRequest'] = _SUBSCRIBELOGSREQUEST -DESCRIPTOR.message_types_by_name['SubscribeLogsResponse'] = _SUBSCRIBELOGSRESPONSE -DESCRIPTOR.message_types_by_name['SubscribeServiceCallsRequest'] = _SUBSCRIBESERVICECALLSREQUEST -DESCRIPTOR.message_types_by_name['ServiceCallResponse'] = _SERVICECALLRESPONSE -DESCRIPTOR.message_types_by_name['SubscribeHomeAssistantStatesRequest'] = _SUBSCRIBEHOMEASSISTANTSTATESREQUEST -DESCRIPTOR.message_types_by_name['SubscribeHomeAssistantStateResponse'] = _SUBSCRIBEHOMEASSISTANTSTATERESPONSE -DESCRIPTOR.message_types_by_name['HomeAssistantStateResponse'] = _HOMEASSISTANTSTATERESPONSE -DESCRIPTOR.message_types_by_name['GetTimeRequest'] = _GETTIMEREQUEST -DESCRIPTOR.message_types_by_name['GetTimeResponse'] = _GETTIMERESPONSE -DESCRIPTOR.enum_types_by_name['FanSpeed'] = _FANSPEED -DESCRIPTOR.enum_types_by_name['LogLevel'] = _LOGLEVEL +_SERVICECALLRESPONSE.fields_by_name[ + "data" +].message_type = _SERVICECALLRESPONSE_DATAENTRY +_SERVICECALLRESPONSE.fields_by_name[ + "data_template" +].message_type = _SERVICECALLRESPONSE_DATATEMPLATEENTRY +_SERVICECALLRESPONSE.fields_by_name[ + "variables" +].message_type = _SERVICECALLRESPONSE_VARIABLESENTRY +DESCRIPTOR.message_types_by_name["HelloRequest"] = _HELLOREQUEST +DESCRIPTOR.message_types_by_name["HelloResponse"] = _HELLORESPONSE +DESCRIPTOR.message_types_by_name["ConnectRequest"] = _CONNECTREQUEST +DESCRIPTOR.message_types_by_name["ConnectResponse"] = _CONNECTRESPONSE +DESCRIPTOR.message_types_by_name["DisconnectRequest"] = _DISCONNECTREQUEST +DESCRIPTOR.message_types_by_name["DisconnectResponse"] = _DISCONNECTRESPONSE +DESCRIPTOR.message_types_by_name["PingRequest"] = _PINGREQUEST +DESCRIPTOR.message_types_by_name["PingResponse"] = _PINGRESPONSE +DESCRIPTOR.message_types_by_name["DeviceInfoRequest"] = _DEVICEINFOREQUEST +DESCRIPTOR.message_types_by_name["DeviceInfoResponse"] = _DEVICEINFORESPONSE +DESCRIPTOR.message_types_by_name["ListEntitiesRequest"] = _LISTENTITIESREQUEST +DESCRIPTOR.message_types_by_name[ + "ListEntitiesBinarySensorResponse" +] = _LISTENTITIESBINARYSENSORRESPONSE +DESCRIPTOR.message_types_by_name[ + "ListEntitiesCoverResponse" +] = _LISTENTITIESCOVERRESPONSE +DESCRIPTOR.message_types_by_name["ListEntitiesFanResponse"] = _LISTENTITIESFANRESPONSE +DESCRIPTOR.message_types_by_name[ + "ListEntitiesLightResponse" +] = _LISTENTITIESLIGHTRESPONSE +DESCRIPTOR.message_types_by_name[ + "ListEntitiesSensorResponse" +] = _LISTENTITIESSENSORRESPONSE +DESCRIPTOR.message_types_by_name[ + "ListEntitiesSwitchResponse" +] = _LISTENTITIESSWITCHRESPONSE +DESCRIPTOR.message_types_by_name[ + "ListEntitiesTextSensorResponse" +] = _LISTENTITIESTEXTSENSORRESPONSE +DESCRIPTOR.message_types_by_name["ListEntitiesDoneResponse"] = _LISTENTITIESDONERESPONSE +DESCRIPTOR.message_types_by_name["SubscribeStatesRequest"] = _SUBSCRIBESTATESREQUEST +DESCRIPTOR.message_types_by_name[ + "BinarySensorStateResponse" +] = _BINARYSENSORSTATERESPONSE +DESCRIPTOR.message_types_by_name["CoverStateResponse"] = _COVERSTATERESPONSE +DESCRIPTOR.message_types_by_name["FanStateResponse"] = _FANSTATERESPONSE +DESCRIPTOR.message_types_by_name["LightStateResponse"] = _LIGHTSTATERESPONSE +DESCRIPTOR.message_types_by_name["SensorStateResponse"] = _SENSORSTATERESPONSE +DESCRIPTOR.message_types_by_name["SwitchStateResponse"] = _SWITCHSTATERESPONSE +DESCRIPTOR.message_types_by_name["TextSensorStateResponse"] = _TEXTSENSORSTATERESPONSE +DESCRIPTOR.message_types_by_name["CoverCommandRequest"] = _COVERCOMMANDREQUEST +DESCRIPTOR.message_types_by_name["FanCommandRequest"] = _FANCOMMANDREQUEST +DESCRIPTOR.message_types_by_name["LightCommandRequest"] = _LIGHTCOMMANDREQUEST +DESCRIPTOR.message_types_by_name["SwitchCommandRequest"] = _SWITCHCOMMANDREQUEST +DESCRIPTOR.message_types_by_name["SubscribeLogsRequest"] = _SUBSCRIBELOGSREQUEST +DESCRIPTOR.message_types_by_name["SubscribeLogsResponse"] = _SUBSCRIBELOGSRESPONSE +DESCRIPTOR.message_types_by_name[ + "SubscribeServiceCallsRequest" +] = _SUBSCRIBESERVICECALLSREQUEST +DESCRIPTOR.message_types_by_name["ServiceCallResponse"] = _SERVICECALLRESPONSE +DESCRIPTOR.message_types_by_name[ + "SubscribeHomeAssistantStatesRequest" +] = _SUBSCRIBEHOMEASSISTANTSTATESREQUEST +DESCRIPTOR.message_types_by_name[ + "SubscribeHomeAssistantStateResponse" +] = _SUBSCRIBEHOMEASSISTANTSTATERESPONSE +DESCRIPTOR.message_types_by_name[ + "HomeAssistantStateResponse" +] = _HOMEASSISTANTSTATERESPONSE +DESCRIPTOR.message_types_by_name["GetTimeRequest"] = _GETTIMEREQUEST +DESCRIPTOR.message_types_by_name["GetTimeResponse"] = _GETTIMERESPONSE +DESCRIPTOR.enum_types_by_name["FanSpeed"] = _FANSPEED +DESCRIPTOR.enum_types_by_name["LogLevel"] = _LOGLEVEL _sym_db.RegisterFileDescriptor(DESCRIPTOR) -HelloRequest = _reflection.GeneratedProtocolMessageType('HelloRequest', (_message.Message,), dict( - DESCRIPTOR = _HELLOREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:HelloRequest) - )) +HelloRequest = _reflection.GeneratedProtocolMessageType( + "HelloRequest", + (_message.Message,), + dict( + DESCRIPTOR=_HELLOREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:HelloRequest) + ), +) _sym_db.RegisterMessage(HelloRequest) -HelloResponse = _reflection.GeneratedProtocolMessageType('HelloResponse', (_message.Message,), dict( - DESCRIPTOR = _HELLORESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:HelloResponse) - )) +HelloResponse = _reflection.GeneratedProtocolMessageType( + "HelloResponse", + (_message.Message,), + dict( + DESCRIPTOR=_HELLORESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:HelloResponse) + ), +) _sym_db.RegisterMessage(HelloResponse) -ConnectRequest = _reflection.GeneratedProtocolMessageType('ConnectRequest', (_message.Message,), dict( - DESCRIPTOR = _CONNECTREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ConnectRequest) - )) +ConnectRequest = _reflection.GeneratedProtocolMessageType( + "ConnectRequest", + (_message.Message,), + dict( + DESCRIPTOR=_CONNECTREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ConnectRequest) + ), +) _sym_db.RegisterMessage(ConnectRequest) -ConnectResponse = _reflection.GeneratedProtocolMessageType('ConnectResponse', (_message.Message,), dict( - DESCRIPTOR = _CONNECTRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ConnectResponse) - )) +ConnectResponse = _reflection.GeneratedProtocolMessageType( + "ConnectResponse", + (_message.Message,), + dict( + DESCRIPTOR=_CONNECTRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ConnectResponse) + ), +) _sym_db.RegisterMessage(ConnectResponse) -DisconnectRequest = _reflection.GeneratedProtocolMessageType('DisconnectRequest', (_message.Message,), dict( - DESCRIPTOR = _DISCONNECTREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:DisconnectRequest) - )) +DisconnectRequest = _reflection.GeneratedProtocolMessageType( + "DisconnectRequest", + (_message.Message,), + dict( + DESCRIPTOR=_DISCONNECTREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:DisconnectRequest) + ), +) _sym_db.RegisterMessage(DisconnectRequest) -DisconnectResponse = _reflection.GeneratedProtocolMessageType('DisconnectResponse', (_message.Message,), dict( - DESCRIPTOR = _DISCONNECTRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:DisconnectResponse) - )) +DisconnectResponse = _reflection.GeneratedProtocolMessageType( + "DisconnectResponse", + (_message.Message,), + dict( + DESCRIPTOR=_DISCONNECTRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:DisconnectResponse) + ), +) _sym_db.RegisterMessage(DisconnectResponse) -PingRequest = _reflection.GeneratedProtocolMessageType('PingRequest', (_message.Message,), dict( - DESCRIPTOR = _PINGREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:PingRequest) - )) +PingRequest = _reflection.GeneratedProtocolMessageType( + "PingRequest", + (_message.Message,), + dict( + DESCRIPTOR=_PINGREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:PingRequest) + ), +) _sym_db.RegisterMessage(PingRequest) -PingResponse = _reflection.GeneratedProtocolMessageType('PingResponse', (_message.Message,), dict( - DESCRIPTOR = _PINGRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:PingResponse) - )) +PingResponse = _reflection.GeneratedProtocolMessageType( + "PingResponse", + (_message.Message,), + dict( + DESCRIPTOR=_PINGRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:PingResponse) + ), +) _sym_db.RegisterMessage(PingResponse) -DeviceInfoRequest = _reflection.GeneratedProtocolMessageType('DeviceInfoRequest', (_message.Message,), dict( - DESCRIPTOR = _DEVICEINFOREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:DeviceInfoRequest) - )) +DeviceInfoRequest = _reflection.GeneratedProtocolMessageType( + "DeviceInfoRequest", + (_message.Message,), + dict( + DESCRIPTOR=_DEVICEINFOREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:DeviceInfoRequest) + ), +) _sym_db.RegisterMessage(DeviceInfoRequest) -DeviceInfoResponse = _reflection.GeneratedProtocolMessageType('DeviceInfoResponse', (_message.Message,), dict( - DESCRIPTOR = _DEVICEINFORESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:DeviceInfoResponse) - )) +DeviceInfoResponse = _reflection.GeneratedProtocolMessageType( + "DeviceInfoResponse", + (_message.Message,), + dict( + DESCRIPTOR=_DEVICEINFORESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:DeviceInfoResponse) + ), +) _sym_db.RegisterMessage(DeviceInfoResponse) -ListEntitiesRequest = _reflection.GeneratedProtocolMessageType('ListEntitiesRequest', (_message.Message,), dict( - DESCRIPTOR = _LISTENTITIESREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ListEntitiesRequest) - )) +ListEntitiesRequest = _reflection.GeneratedProtocolMessageType( + "ListEntitiesRequest", + (_message.Message,), + dict( + DESCRIPTOR=_LISTENTITIESREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ListEntitiesRequest) + ), +) _sym_db.RegisterMessage(ListEntitiesRequest) -ListEntitiesBinarySensorResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesBinarySensorResponse', (_message.Message,), dict( - DESCRIPTOR = _LISTENTITIESBINARYSENSORRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ListEntitiesBinarySensorResponse) - )) +ListEntitiesBinarySensorResponse = _reflection.GeneratedProtocolMessageType( + "ListEntitiesBinarySensorResponse", + (_message.Message,), + dict( + DESCRIPTOR=_LISTENTITIESBINARYSENSORRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ListEntitiesBinarySensorResponse) + ), +) _sym_db.RegisterMessage(ListEntitiesBinarySensorResponse) -ListEntitiesCoverResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesCoverResponse', (_message.Message,), dict( - DESCRIPTOR = _LISTENTITIESCOVERRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ListEntitiesCoverResponse) - )) +ListEntitiesCoverResponse = _reflection.GeneratedProtocolMessageType( + "ListEntitiesCoverResponse", + (_message.Message,), + dict( + DESCRIPTOR=_LISTENTITIESCOVERRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ListEntitiesCoverResponse) + ), +) _sym_db.RegisterMessage(ListEntitiesCoverResponse) -ListEntitiesFanResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesFanResponse', (_message.Message,), dict( - DESCRIPTOR = _LISTENTITIESFANRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ListEntitiesFanResponse) - )) +ListEntitiesFanResponse = _reflection.GeneratedProtocolMessageType( + "ListEntitiesFanResponse", + (_message.Message,), + dict( + DESCRIPTOR=_LISTENTITIESFANRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ListEntitiesFanResponse) + ), +) _sym_db.RegisterMessage(ListEntitiesFanResponse) -ListEntitiesLightResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesLightResponse', (_message.Message,), dict( - DESCRIPTOR = _LISTENTITIESLIGHTRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ListEntitiesLightResponse) - )) +ListEntitiesLightResponse = _reflection.GeneratedProtocolMessageType( + "ListEntitiesLightResponse", + (_message.Message,), + dict( + DESCRIPTOR=_LISTENTITIESLIGHTRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ListEntitiesLightResponse) + ), +) _sym_db.RegisterMessage(ListEntitiesLightResponse) -ListEntitiesSensorResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesSensorResponse', (_message.Message,), dict( - DESCRIPTOR = _LISTENTITIESSENSORRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ListEntitiesSensorResponse) - )) +ListEntitiesSensorResponse = _reflection.GeneratedProtocolMessageType( + "ListEntitiesSensorResponse", + (_message.Message,), + dict( + DESCRIPTOR=_LISTENTITIESSENSORRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ListEntitiesSensorResponse) + ), +) _sym_db.RegisterMessage(ListEntitiesSensorResponse) -ListEntitiesSwitchResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesSwitchResponse', (_message.Message,), dict( - DESCRIPTOR = _LISTENTITIESSWITCHRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ListEntitiesSwitchResponse) - )) +ListEntitiesSwitchResponse = _reflection.GeneratedProtocolMessageType( + "ListEntitiesSwitchResponse", + (_message.Message,), + dict( + DESCRIPTOR=_LISTENTITIESSWITCHRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ListEntitiesSwitchResponse) + ), +) _sym_db.RegisterMessage(ListEntitiesSwitchResponse) -ListEntitiesTextSensorResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesTextSensorResponse', (_message.Message,), dict( - DESCRIPTOR = _LISTENTITIESTEXTSENSORRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ListEntitiesTextSensorResponse) - )) +ListEntitiesTextSensorResponse = _reflection.GeneratedProtocolMessageType( + "ListEntitiesTextSensorResponse", + (_message.Message,), + dict( + DESCRIPTOR=_LISTENTITIESTEXTSENSORRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ListEntitiesTextSensorResponse) + ), +) _sym_db.RegisterMessage(ListEntitiesTextSensorResponse) -ListEntitiesDoneResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesDoneResponse', (_message.Message,), dict( - DESCRIPTOR = _LISTENTITIESDONERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ListEntitiesDoneResponse) - )) +ListEntitiesDoneResponse = _reflection.GeneratedProtocolMessageType( + "ListEntitiesDoneResponse", + (_message.Message,), + dict( + DESCRIPTOR=_LISTENTITIESDONERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ListEntitiesDoneResponse) + ), +) _sym_db.RegisterMessage(ListEntitiesDoneResponse) -SubscribeStatesRequest = _reflection.GeneratedProtocolMessageType('SubscribeStatesRequest', (_message.Message,), dict( - DESCRIPTOR = _SUBSCRIBESTATESREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:SubscribeStatesRequest) - )) +SubscribeStatesRequest = _reflection.GeneratedProtocolMessageType( + "SubscribeStatesRequest", + (_message.Message,), + dict( + DESCRIPTOR=_SUBSCRIBESTATESREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:SubscribeStatesRequest) + ), +) _sym_db.RegisterMessage(SubscribeStatesRequest) -BinarySensorStateResponse = _reflection.GeneratedProtocolMessageType('BinarySensorStateResponse', (_message.Message,), dict( - DESCRIPTOR = _BINARYSENSORSTATERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:BinarySensorStateResponse) - )) +BinarySensorStateResponse = _reflection.GeneratedProtocolMessageType( + "BinarySensorStateResponse", + (_message.Message,), + dict( + DESCRIPTOR=_BINARYSENSORSTATERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:BinarySensorStateResponse) + ), +) _sym_db.RegisterMessage(BinarySensorStateResponse) -CoverStateResponse = _reflection.GeneratedProtocolMessageType('CoverStateResponse', (_message.Message,), dict( - DESCRIPTOR = _COVERSTATERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:CoverStateResponse) - )) +CoverStateResponse = _reflection.GeneratedProtocolMessageType( + "CoverStateResponse", + (_message.Message,), + dict( + DESCRIPTOR=_COVERSTATERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:CoverStateResponse) + ), +) _sym_db.RegisterMessage(CoverStateResponse) -FanStateResponse = _reflection.GeneratedProtocolMessageType('FanStateResponse', (_message.Message,), dict( - DESCRIPTOR = _FANSTATERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:FanStateResponse) - )) +FanStateResponse = _reflection.GeneratedProtocolMessageType( + "FanStateResponse", + (_message.Message,), + dict( + DESCRIPTOR=_FANSTATERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:FanStateResponse) + ), +) _sym_db.RegisterMessage(FanStateResponse) -LightStateResponse = _reflection.GeneratedProtocolMessageType('LightStateResponse', (_message.Message,), dict( - DESCRIPTOR = _LIGHTSTATERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:LightStateResponse) - )) +LightStateResponse = _reflection.GeneratedProtocolMessageType( + "LightStateResponse", + (_message.Message,), + dict( + DESCRIPTOR=_LIGHTSTATERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:LightStateResponse) + ), +) _sym_db.RegisterMessage(LightStateResponse) -SensorStateResponse = _reflection.GeneratedProtocolMessageType('SensorStateResponse', (_message.Message,), dict( - DESCRIPTOR = _SENSORSTATERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:SensorStateResponse) - )) +SensorStateResponse = _reflection.GeneratedProtocolMessageType( + "SensorStateResponse", + (_message.Message,), + dict( + DESCRIPTOR=_SENSORSTATERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:SensorStateResponse) + ), +) _sym_db.RegisterMessage(SensorStateResponse) -SwitchStateResponse = _reflection.GeneratedProtocolMessageType('SwitchStateResponse', (_message.Message,), dict( - DESCRIPTOR = _SWITCHSTATERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:SwitchStateResponse) - )) +SwitchStateResponse = _reflection.GeneratedProtocolMessageType( + "SwitchStateResponse", + (_message.Message,), + dict( + DESCRIPTOR=_SWITCHSTATERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:SwitchStateResponse) + ), +) _sym_db.RegisterMessage(SwitchStateResponse) -TextSensorStateResponse = _reflection.GeneratedProtocolMessageType('TextSensorStateResponse', (_message.Message,), dict( - DESCRIPTOR = _TEXTSENSORSTATERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:TextSensorStateResponse) - )) +TextSensorStateResponse = _reflection.GeneratedProtocolMessageType( + "TextSensorStateResponse", + (_message.Message,), + dict( + DESCRIPTOR=_TEXTSENSORSTATERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:TextSensorStateResponse) + ), +) _sym_db.RegisterMessage(TextSensorStateResponse) -CoverCommandRequest = _reflection.GeneratedProtocolMessageType('CoverCommandRequest', (_message.Message,), dict( - DESCRIPTOR = _COVERCOMMANDREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:CoverCommandRequest) - )) +CoverCommandRequest = _reflection.GeneratedProtocolMessageType( + "CoverCommandRequest", + (_message.Message,), + dict( + DESCRIPTOR=_COVERCOMMANDREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:CoverCommandRequest) + ), +) _sym_db.RegisterMessage(CoverCommandRequest) -FanCommandRequest = _reflection.GeneratedProtocolMessageType('FanCommandRequest', (_message.Message,), dict( - DESCRIPTOR = _FANCOMMANDREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:FanCommandRequest) - )) +FanCommandRequest = _reflection.GeneratedProtocolMessageType( + "FanCommandRequest", + (_message.Message,), + dict( + DESCRIPTOR=_FANCOMMANDREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:FanCommandRequest) + ), +) _sym_db.RegisterMessage(FanCommandRequest) -LightCommandRequest = _reflection.GeneratedProtocolMessageType('LightCommandRequest', (_message.Message,), dict( - DESCRIPTOR = _LIGHTCOMMANDREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:LightCommandRequest) - )) +LightCommandRequest = _reflection.GeneratedProtocolMessageType( + "LightCommandRequest", + (_message.Message,), + dict( + DESCRIPTOR=_LIGHTCOMMANDREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:LightCommandRequest) + ), +) _sym_db.RegisterMessage(LightCommandRequest) -SwitchCommandRequest = _reflection.GeneratedProtocolMessageType('SwitchCommandRequest', (_message.Message,), dict( - DESCRIPTOR = _SWITCHCOMMANDREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:SwitchCommandRequest) - )) +SwitchCommandRequest = _reflection.GeneratedProtocolMessageType( + "SwitchCommandRequest", + (_message.Message,), + dict( + DESCRIPTOR=_SWITCHCOMMANDREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:SwitchCommandRequest) + ), +) _sym_db.RegisterMessage(SwitchCommandRequest) -SubscribeLogsRequest = _reflection.GeneratedProtocolMessageType('SubscribeLogsRequest', (_message.Message,), dict( - DESCRIPTOR = _SUBSCRIBELOGSREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:SubscribeLogsRequest) - )) +SubscribeLogsRequest = _reflection.GeneratedProtocolMessageType( + "SubscribeLogsRequest", + (_message.Message,), + dict( + DESCRIPTOR=_SUBSCRIBELOGSREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:SubscribeLogsRequest) + ), +) _sym_db.RegisterMessage(SubscribeLogsRequest) -SubscribeLogsResponse = _reflection.GeneratedProtocolMessageType('SubscribeLogsResponse', (_message.Message,), dict( - DESCRIPTOR = _SUBSCRIBELOGSRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:SubscribeLogsResponse) - )) +SubscribeLogsResponse = _reflection.GeneratedProtocolMessageType( + "SubscribeLogsResponse", + (_message.Message,), + dict( + DESCRIPTOR=_SUBSCRIBELOGSRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:SubscribeLogsResponse) + ), +) _sym_db.RegisterMessage(SubscribeLogsResponse) -SubscribeServiceCallsRequest = _reflection.GeneratedProtocolMessageType('SubscribeServiceCallsRequest', (_message.Message,), dict( - DESCRIPTOR = _SUBSCRIBESERVICECALLSREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:SubscribeServiceCallsRequest) - )) +SubscribeServiceCallsRequest = _reflection.GeneratedProtocolMessageType( + "SubscribeServiceCallsRequest", + (_message.Message,), + dict( + DESCRIPTOR=_SUBSCRIBESERVICECALLSREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:SubscribeServiceCallsRequest) + ), +) _sym_db.RegisterMessage(SubscribeServiceCallsRequest) -ServiceCallResponse = _reflection.GeneratedProtocolMessageType('ServiceCallResponse', (_message.Message,), dict( - - DataEntry = _reflection.GeneratedProtocolMessageType('DataEntry', (_message.Message,), dict( - DESCRIPTOR = _SERVICECALLRESPONSE_DATAENTRY, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataEntry) - )) - , - - DataTemplateEntry = _reflection.GeneratedProtocolMessageType('DataTemplateEntry', (_message.Message,), dict( - DESCRIPTOR = _SERVICECALLRESPONSE_DATATEMPLATEENTRY, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataTemplateEntry) - )) - , - - VariablesEntry = _reflection.GeneratedProtocolMessageType('VariablesEntry', (_message.Message,), dict( - DESCRIPTOR = _SERVICECALLRESPONSE_VARIABLESENTRY, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ServiceCallResponse.VariablesEntry) - )) - , - DESCRIPTOR = _SERVICECALLRESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:ServiceCallResponse) - )) +ServiceCallResponse = _reflection.GeneratedProtocolMessageType( + "ServiceCallResponse", + (_message.Message,), + dict( + DataEntry=_reflection.GeneratedProtocolMessageType( + "DataEntry", + (_message.Message,), + dict( + DESCRIPTOR=_SERVICECALLRESPONSE_DATAENTRY, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataEntry) + ), + ), + DataTemplateEntry=_reflection.GeneratedProtocolMessageType( + "DataTemplateEntry", + (_message.Message,), + dict( + DESCRIPTOR=_SERVICECALLRESPONSE_DATATEMPLATEENTRY, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataTemplateEntry) + ), + ), + VariablesEntry=_reflection.GeneratedProtocolMessageType( + "VariablesEntry", + (_message.Message,), + dict( + DESCRIPTOR=_SERVICECALLRESPONSE_VARIABLESENTRY, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ServiceCallResponse.VariablesEntry) + ), + ), + DESCRIPTOR=_SERVICECALLRESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:ServiceCallResponse) + ), +) _sym_db.RegisterMessage(ServiceCallResponse) _sym_db.RegisterMessage(ServiceCallResponse.DataEntry) _sym_db.RegisterMessage(ServiceCallResponse.DataTemplateEntry) _sym_db.RegisterMessage(ServiceCallResponse.VariablesEntry) -SubscribeHomeAssistantStatesRequest = _reflection.GeneratedProtocolMessageType('SubscribeHomeAssistantStatesRequest', (_message.Message,), dict( - DESCRIPTOR = _SUBSCRIBEHOMEASSISTANTSTATESREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStatesRequest) - )) +SubscribeHomeAssistantStatesRequest = _reflection.GeneratedProtocolMessageType( + "SubscribeHomeAssistantStatesRequest", + (_message.Message,), + dict( + DESCRIPTOR=_SUBSCRIBEHOMEASSISTANTSTATESREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStatesRequest) + ), +) _sym_db.RegisterMessage(SubscribeHomeAssistantStatesRequest) -SubscribeHomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType('SubscribeHomeAssistantStateResponse', (_message.Message,), dict( - DESCRIPTOR = _SUBSCRIBEHOMEASSISTANTSTATERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStateResponse) - )) +SubscribeHomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType( + "SubscribeHomeAssistantStateResponse", + (_message.Message,), + dict( + DESCRIPTOR=_SUBSCRIBEHOMEASSISTANTSTATERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStateResponse) + ), +) _sym_db.RegisterMessage(SubscribeHomeAssistantStateResponse) -HomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType('HomeAssistantStateResponse', (_message.Message,), dict( - DESCRIPTOR = _HOMEASSISTANTSTATERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:HomeAssistantStateResponse) - )) +HomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType( + "HomeAssistantStateResponse", + (_message.Message,), + dict( + DESCRIPTOR=_HOMEASSISTANTSTATERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:HomeAssistantStateResponse) + ), +) _sym_db.RegisterMessage(HomeAssistantStateResponse) -GetTimeRequest = _reflection.GeneratedProtocolMessageType('GetTimeRequest', (_message.Message,), dict( - DESCRIPTOR = _GETTIMEREQUEST, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:GetTimeRequest) - )) +GetTimeRequest = _reflection.GeneratedProtocolMessageType( + "GetTimeRequest", + (_message.Message,), + dict( + DESCRIPTOR=_GETTIMEREQUEST, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:GetTimeRequest) + ), +) _sym_db.RegisterMessage(GetTimeRequest) -GetTimeResponse = _reflection.GeneratedProtocolMessageType('GetTimeResponse', (_message.Message,), dict( - DESCRIPTOR = _GETTIMERESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:GetTimeResponse) - )) +GetTimeResponse = _reflection.GeneratedProtocolMessageType( + "GetTimeResponse", + (_message.Message,), + dict( + DESCRIPTOR=_GETTIMERESPONSE, + __module__="api_pb2" + # @@protoc_insertion_point(class_scope:GetTimeResponse) + ), +) _sym_db.RegisterMessage(GetTimeResponse) diff --git a/esphome/api/client.py b/esphome/api/client.py index a32c23981968..84c9890fe0a9 100644 --- a/esphome/api/client.py +++ b/esphome/api/client.py @@ -177,10 +177,14 @@ def connect(self): try: ip = resolve_ip_address(self._address) except EsphomeError as err: - _LOGGER.warning("Error resolving IP address of %s. Is it connected to WiFi?", - self._address) - _LOGGER.warning("(If this error persists, please set a static IP address: " - "https://esphome.io/components/wifi.html#manual-ips)") + _LOGGER.warning( + "Error resolving IP address of %s. Is it connected to WiFi?", + self._address, + ) + _LOGGER.warning( + "(If this error persists, please set a static IP address: " + "https://esphome.io/components/wifi.html#manual-ips)" + ) raise APIConnectionError(err) from err _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip) @@ -198,14 +202,19 @@ def connect(self): self._socket_open_event.set() hello = pb.HelloRequest() - hello.client_info = f'ESPHome v{const.__version__}' + hello.client_info = f"ESPHome v{const.__version__}" try: resp = self._send_message_await_response(hello, pb.HelloResponse) except APIConnectionError as err: self._fatal_error(err) raise err - _LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address, - resp.server_info, resp.api_version_major, resp.api_version_minor) + _LOGGER.debug( + "Successfully connected to %s ('%s' API=%s.%s)", + self._address, + resp.server_info, + resp.api_version_major, + resp.api_version_minor, + ) self._connected = True self._refresh_ping() if self.on_connect is not None: @@ -270,7 +279,9 @@ def _send_message(self, msg): req += encoded self._write(req) - def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=5): + def _send_message_await_response_complex( + self, send_msg, do_append, do_stop, timeout=5 + ): event = threading.Event() responses = [] @@ -295,12 +306,15 @@ def _send_message_await_response(self, send_msg, response_type, timeout=5): def is_response(msg): return isinstance(msg, response_type) - return self._send_message_await_response_complex(send_msg, is_response, is_response, - timeout)[0] + return self._send_message_await_response_complex( + send_msg, is_response, is_response, timeout + )[0] def device_info(self): self._check_connected() - return self._send_message_await_response(pb.DeviceInfoRequest(), pb.DeviceInfoResponse) + return self._send_message_await_response( + pb.DeviceInfoRequest(), pb.DeviceInfoResponse + ) def ping(self): self._check_connected() @@ -310,7 +324,9 @@ def disconnect(self, on_disconnect=True): self._check_connected() try: - self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse) + self._send_message_await_response( + pb.DisconnectRequest(), pb.DisconnectResponse + ) except APIConnectionError: pass self._close_socket() @@ -415,7 +431,7 @@ def _handle_internal_messages(self, msg): def run_logs(config, address): - conf = config['api'] + conf = config["api"] port = conf[CONF_PORT] password = conf[CONF_PASSWORD] _LOGGER.info("Starting log output from %s using esphome API", address) @@ -447,24 +463,35 @@ def try_connect(err, tries=0): _LOGGER.info("Successfully connected to %s", address) return - wait_time = int(min(1.5**min(tries, 100), 30)) + wait_time = int(min(1.5 ** min(tries, 100), 30)) if not has_connects: - _LOGGER.warning("Initial connection failed. The ESP might not be connected " - "to WiFi yet (%s). Re-Trying in %s seconds", - error, wait_time) + _LOGGER.warning( + "Initial connection failed. The ESP might not be connected " + "to WiFi yet (%s). Re-Trying in %s seconds", + error, + wait_time, + ) else: - _LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds", - error, wait_time) - timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1)) + _LOGGER.warning( + "Couldn't connect to API (%s). Trying to reconnect in %s seconds", + error, + wait_time, + ) + timer = threading.Timer( + wait_time, functools.partial(try_connect, None, tries + 1) + ) timer.start() retry_timer.append(timer) def on_log(msg): - time_ = datetime.now().time().strftime('[%H:%M:%S]') + time_ = datetime.now().time().strftime("[%H:%M:%S]") text = msg.message if msg.send_failed: - text = color('white', '(Message skipped because it was too big to fit in ' - 'TCP buffer - This is only cosmetic)') + text = color( + "white", + "(Message skipped because it was too big to fit in " + "TCP buffer - This is only cosmetic)", + ) safe_print(time_ + text) def on_login(): diff --git a/esphome/automation.py b/esphome/automation.py index 4b5e39b0f5f0..eb6cb025329a 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -1,8 +1,17 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \ - CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME +from esphome.const import ( + CONF_AUTOMATION_ID, + CONF_CONDITION, + CONF_ELSE, + CONF_ID, + CONF_THEN, + CONF_TRIGGER_ID, + CONF_TYPE_ID, + CONF_TIME, +) from esphome.core import coroutine +from esphome.jsonschema import jschema_extractor from esphome.util import Registry @@ -13,7 +22,12 @@ def maybe_simple_id(*validators): def maybe_conf(conf, *validators): validator = cv.All(*validators) + @jschema_extractor("maybe") def validate(value): + # pylint: disable=comparison-with-callable + if value == jschema_extractor: + return validator + if isinstance(value, dict): return validator(value) with cv.remove_prepend_path([conf]): @@ -30,36 +44,34 @@ def register_condition(name, condition_type, schema): return CONDITION_REGISTRY.register(name, condition_type, schema) -Action = cg.esphome_ns.class_('Action') -Trigger = cg.esphome_ns.class_('Trigger') +Action = cg.esphome_ns.class_("Action") +Trigger = cg.esphome_ns.class_("Trigger") ACTION_REGISTRY = Registry() -Condition = cg.esphome_ns.class_('Condition') +Condition = cg.esphome_ns.class_("Condition") CONDITION_REGISTRY = Registry() -validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY) -validate_action_list = cv.validate_registry('action', ACTION_REGISTRY) -validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY) -validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY) +validate_action = cv.validate_registry_entry("action", ACTION_REGISTRY) +validate_action_list = cv.validate_registry("action", ACTION_REGISTRY) +validate_condition = cv.validate_registry_entry("condition", CONDITION_REGISTRY) +validate_condition_list = cv.validate_registry("condition", CONDITION_REGISTRY) def validate_potentially_and_condition(value): if isinstance(value, list): - with cv.remove_prepend_path(['and']): - return validate_condition({ - 'and': value - }) + with cv.remove_prepend_path(["and"]): + return validate_condition({"and": value}) return validate_condition(value) -DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component) -LambdaAction = cg.esphome_ns.class_('LambdaAction', Action) -IfAction = cg.esphome_ns.class_('IfAction', Action) -WhileAction = cg.esphome_ns.class_('WhileAction', Action) -WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component) -UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action) -Automation = cg.esphome_ns.class_('Automation') +DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) +LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) +IfAction = cg.esphome_ns.class_("IfAction", Action) +WhileAction = cg.esphome_ns.class_("WhileAction", Action) +WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) +UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) +Automation = cg.esphome_ns.class_("Automation") -LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition) -ForCondition = cg.esphome_ns.class_('ForCondition', Condition, cg.Component) +LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) +ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component) def validate_automation(extra_schema=None, extra_validators=None, single=False): @@ -83,10 +95,10 @@ def validator_(value): try: return cv.Schema([schema])(value) except cv.Invalid as err2: - if 'extra keys not allowed' in str(err2) and len(err2.path) == 2: + if "extra keys not allowed" in str(err2) and len(err2.path) == 2: # pylint: disable=raise-missing-from raise err - if 'Unable to find action' in str(err): + if "Unable to find action" in str(err): raise err2 raise cv.MultipleInvalid([err, err2]) elif isinstance(value, dict): @@ -97,7 +109,13 @@ def validator_(value): # This should only happen with invalid configs, but let's have a nice error message. return [schema(value)] + @jschema_extractor("automation") def validator(value): + # hack to get the schema + # pylint: disable=comparison-with-callable + if value == jschema_extractor: + return schema + value = validator_(value) if extra_validators is not None: value = cv.Schema([extra_validators])(value) @@ -110,47 +128,59 @@ def validator(value): return validator -AUTOMATION_SCHEMA = cv.Schema({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger), - cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation), - cv.Required(CONF_THEN): validate_action_list, -}) +AUTOMATION_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger), + cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation), + cv.Required(CONF_THEN): validate_action_list, + } +) -AndCondition = cg.esphome_ns.class_('AndCondition', Condition) -OrCondition = cg.esphome_ns.class_('OrCondition', Condition) -NotCondition = cg.esphome_ns.class_('NotCondition', Condition) +AndCondition = cg.esphome_ns.class_("AndCondition", Condition) +OrCondition = cg.esphome_ns.class_("OrCondition", Condition) +NotCondition = cg.esphome_ns.class_("NotCondition", Condition) -@register_condition('and', AndCondition, validate_condition_list) +@register_condition("and", AndCondition, validate_condition_list) def and_condition_to_code(config, condition_id, template_arg, args): conditions = yield build_condition_list(config, template_arg, args) yield cg.new_Pvariable(condition_id, template_arg, conditions) -@register_condition('or', OrCondition, validate_condition_list) +@register_condition("or", OrCondition, validate_condition_list) def or_condition_to_code(config, condition_id, template_arg, args): conditions = yield build_condition_list(config, template_arg, args) yield cg.new_Pvariable(condition_id, template_arg, conditions) -@register_condition('not', NotCondition, validate_potentially_and_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) -@register_condition('lambda', LambdaCondition, cv.lambda_) +@register_condition("lambda", LambdaCondition, cv.lambda_) def lambda_condition_to_code(config, condition_id, template_arg, args): lambda_ = yield cg.process_lambda(config, args, return_type=bool) yield cg.new_Pvariable(condition_id, template_arg, lambda_) -@register_condition('for', ForCondition, cv.Schema({ - cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds), - cv.Required(CONF_CONDITION): validate_potentially_and_condition, -}).extend(cv.COMPONENT_SCHEMA)) +@register_condition( + "for", + ForCondition, + cv.Schema( + { + cv.Required(CONF_TIME): cv.templatable( + cv.positive_time_period_milliseconds + ), + cv.Required(CONF_CONDITION): validate_potentially_and_condition, + } + ).extend(cv.COMPONENT_SCHEMA), +) def for_condition_to_code(config, condition_id, template_arg, args): - condition = yield build_condition(config[CONF_CONDITION], cg.TemplateArguments(), []) + condition = yield build_condition( + config[CONF_CONDITION], cg.TemplateArguments(), [] + ) var = cg.new_Pvariable(condition_id, template_arg, condition) yield cg.register_component(var, config) templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32) @@ -158,7 +188,9 @@ def for_condition_to_code(config, condition_id, template_arg, args): yield var -@register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds)) +@register_action( + "delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds) +) def delay_action_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_component(var, {}) @@ -167,11 +199,18 @@ def delay_action_to_code(config, action_id, template_arg, args): yield var -@register_action('if', IfAction, cv.All({ - cv.Required(CONF_CONDITION): validate_potentially_and_condition, - cv.Optional(CONF_THEN): validate_action_list, - cv.Optional(CONF_ELSE): validate_action_list, -}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE))) +@register_action( + "if", + IfAction, + cv.All( + { + cv.Required(CONF_CONDITION): validate_potentially_and_condition, + cv.Optional(CONF_THEN): validate_action_list, + cv.Optional(CONF_ELSE): validate_action_list, + }, + cv.has_at_least_one_key(CONF_THEN, CONF_ELSE), + ), +) def if_action_to_code(config, action_id, template_arg, args): conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) var = cg.new_Pvariable(action_id, template_arg, conditions) @@ -184,10 +223,16 @@ def if_action_to_code(config, action_id, template_arg, args): yield var -@register_action('while', WhileAction, cv.Schema({ - cv.Required(CONF_CONDITION): validate_potentially_and_condition, - cv.Required(CONF_THEN): validate_action_list, -})) +@register_action( + "while", + WhileAction, + cv.Schema( + { + cv.Required(CONF_CONDITION): validate_potentially_and_condition, + cv.Required(CONF_THEN): validate_action_list, + } + ), +) def while_action_to_code(config, action_id, template_arg, args): conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) var = cg.new_Pvariable(action_id, template_arg, conditions) @@ -197,15 +242,17 @@ def while_action_to_code(config, action_id, template_arg, args): def validate_wait_until(value): - schema = cv.Schema({ - cv.Required(CONF_CONDITION): validate_potentially_and_condition, - }) + schema = cv.Schema( + { + cv.Required(CONF_CONDITION): validate_potentially_and_condition, + } + ) if isinstance(value, dict) and CONF_CONDITION in value: return schema(value) return validate_wait_until({CONF_CONDITION: value}) -@register_action('wait_until', WaitUntilAction, validate_wait_until) +@register_action("wait_until", WaitUntilAction, validate_wait_until) def wait_until_action_to_code(config, action_id, template_arg, args): conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) var = cg.new_Pvariable(action_id, template_arg, conditions) @@ -213,15 +260,21 @@ def wait_until_action_to_code(config, action_id, template_arg, args): yield var -@register_action('lambda', LambdaAction, cv.lambda_) +@register_action("lambda", LambdaAction, cv.lambda_) def lambda_action_to_code(config, action_id, template_arg, args): lambda_ = yield cg.process_lambda(config, args, return_type=cg.void) yield cg.new_Pvariable(action_id, template_arg, lambda_) -@register_action('component.update', UpdateComponentAction, maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(cg.PollingComponent), -})) +@register_action( + "component.update", + UpdateComponentAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(cg.PollingComponent), + } + ), +) def component_update_action_to_code(config, action_id, template_arg, args): comp = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, comp) @@ -229,7 +282,9 @@ def component_update_action_to_code(config, action_id, template_arg, args): @coroutine def build_action(full_config, template_arg, args): - registry_entry, config = cg.extract_registry_entry_config(ACTION_REGISTRY, full_config) + registry_entry, config = cg.extract_registry_entry_config( + ACTION_REGISTRY, full_config + ) action_id = full_config[CONF_TYPE_ID] builder = registry_entry.coroutine_fun yield builder(config, action_id, template_arg, args) @@ -246,7 +301,9 @@ def build_action_list(config, templ, arg_type): @coroutine def build_condition(full_config, template_arg, args): - registry_entry, config = cg.extract_registry_entry_config(CONDITION_REGISTRY, full_config) + registry_entry, config = cg.extract_registry_entry_config( + CONDITION_REGISTRY, full_config + ) action_id = full_config[CONF_TYPE_ID] builder = registry_entry.coroutine_fun yield builder(config, action_id, template_arg, args) diff --git a/esphome/codegen.py b/esphome/codegen.py index c30b43f9523f..90f59f75bc45 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -9,18 +9,71 @@ # pylint: disable=unused-import from esphome.cpp_generator import ( # noqa - Expression, RawExpression, RawStatement, TemplateArguments, - 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) + Expression, + RawExpression, + RawStatement, + TemplateArguments, + StructInitializer, + ArrayInitializer, + safe_exp, + Statement, + LineComment, + progmem_array, + statement, + variable, + new_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, register_parented) + gpio_pin_expression, + register_component, + build_registry_entry, + build_registry_list, + extract_registry_entry_config, + register_parented, +) from esphome.cpp_types import ( # noqa - global_ns, void, nullptr, float_, double, bool_, int_, 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, - JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin) + global_ns, + void, + nullptr, + float_, + double, + bool_, + int_, + 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, + JsonObjectRef, + JsonObjectConstRef, + Controller, + GPIOPin, +) diff --git a/esphome/components/a4988/a4988.cpp b/esphome/components/a4988/a4988.cpp index 99b677a9ab8a..4100563412c4 100644 --- a/esphome/components/a4988/a4988.cpp +++ b/esphome/components/a4988/a4988.cpp @@ -11,6 +11,7 @@ void A4988::setup() { if (this->sleep_pin_ != nullptr) { this->sleep_pin_->setup(); this->sleep_pin_->digital_write(false); + this->sleep_pin_state_ = false; } this->step_pin_->setup(); this->step_pin_->digital_write(false); @@ -27,7 +28,12 @@ void A4988::dump_config() { void A4988::loop() { bool at_target = this->has_reached_target(); if (this->sleep_pin_ != nullptr) { + bool sleep_rising_edge = !sleep_pin_state_ & !at_target; this->sleep_pin_->digital_write(!at_target); + this->sleep_pin_state_ = !at_target; + if (sleep_rising_edge) { + delayMicroseconds(1000); + } } if (at_target) { this->high_freq_.stop(); diff --git a/esphome/components/a4988/a4988.h b/esphome/components/a4988/a4988.h index 10fb5e001558..5be0f3ce692e 100644 --- a/esphome/components/a4988/a4988.h +++ b/esphome/components/a4988/a4988.h @@ -21,6 +21,7 @@ class A4988 : public stepper::Stepper, public Component { GPIOPin *step_pin_; GPIOPin *dir_pin_; GPIOPin *sleep_pin_{nullptr}; + bool sleep_pin_state_; HighFrequencyLoopRequester high_freq_; }; diff --git a/esphome/components/a4988/stepper.py b/esphome/components/a4988/stepper.py index 29696dbd5ead..1de3562ff7a0 100644 --- a/esphome/components/a4988/stepper.py +++ b/esphome/components/a4988/stepper.py @@ -5,15 +5,17 @@ from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN -a4988_ns = cg.esphome_ns.namespace('a4988') -A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component) +a4988_ns = cg.esphome_ns.namespace("a4988") +A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component) -CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({ - cv.Required(CONF_ID): cv.declare_id(A4988), - cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(A4988), + cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/ac_dimmer/output.py b/esphome/components/ac_dimmer/output.py index 17dcd8ac26a1..2f06a0b6fc95 100644 --- a/esphome/components/ac_dimmer/output.py +++ b/esphome/components/ac_dimmer/output.py @@ -4,28 +4,32 @@ from esphome.components import output from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD -CODEOWNERS = ['@glmnet'] +CODEOWNERS = ["@glmnet"] -ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer') -AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component) +ac_dimmer_ns = cg.esphome_ns.namespace("ac_dimmer") +AcDimmer = ac_dimmer_ns.class_("AcDimmer", output.FloatOutput, cg.Component) -DimMethod = ac_dimmer_ns.enum('DimMethod') +DimMethod = ac_dimmer_ns.enum("DimMethod") DIM_METHODS = { - 'LEADING_PULSE': DimMethod.DIM_METHOD_LEADING_PULSE, - 'LEADING': DimMethod.DIM_METHOD_LEADING, - 'TRAILING': DimMethod.DIM_METHOD_TRAILING, + "LEADING_PULSE": DimMethod.DIM_METHOD_LEADING_PULSE, + "LEADING": DimMethod.DIM_METHOD_LEADING, + "TRAILING": DimMethod.DIM_METHOD_TRAILING, } -CONF_GATE_PIN = 'gate_pin' -CONF_ZERO_CROSS_PIN = 'zero_cross_pin' -CONF_INIT_WITH_HALF_CYCLE = 'init_with_half_cycle' -CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ - cv.Required(CONF_ID): cv.declare_id(AcDimmer), - cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, - cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema, - cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean, - cv.Optional(CONF_METHOD, default='leading pulse'): cv.enum(DIM_METHODS, upper=True, space='_'), -}).extend(cv.COMPONENT_SCHEMA) +CONF_GATE_PIN = "gate_pin" +CONF_ZERO_CROSS_PIN = "zero_cross_pin" +CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle" +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(AcDimmer), + cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean, + cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum( + DIM_METHODS, upper=True, space="_" + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/adalight/__init__.py b/esphome/components/adalight/__init__.py index 66fae17f1e2d..169a13716502 100644 --- a/esphome/components/adalight/__init__.py +++ b/esphome/components/adalight/__init__.py @@ -5,18 +5,22 @@ from esphome.components.light.effects import register_addressable_effect from esphome.const import CONF_NAME, CONF_UART_ID -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] -adalight_ns = cg.esphome_ns.namespace('adalight') +adalight_ns = cg.esphome_ns.namespace("adalight") AdalightLightEffect = adalight_ns.class_( - 'AdalightLightEffect', uart.UARTDevice, AddressableLightEffect) + "AdalightLightEffect", uart.UARTDevice, AddressableLightEffect +) CONFIG_SCHEMA = cv.Schema({}) -@register_addressable_effect('adalight', AdalightLightEffect, "Adalight", { - cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent) -}) +@register_addressable_effect( + "adalight", + AdalightLightEffect, + "Adalight", + {cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)}, +) def adalight_light_effect_to_code(config, effect_id): effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) yield uart.register_uart_device(effect, config) diff --git a/esphome/components/adalight/adalight_light_effect.cpp b/esphome/components/adalight/adalight_light_effect.cpp index 1bf357e3082b..d162c90721b1 100644 --- a/esphome/components/adalight/adalight_light_effect.cpp +++ b/esphome/components/adalight/adalight_light_effect.cpp @@ -42,11 +42,11 @@ void AdalightLightEffect::reset_frame_(light::AddressableLight &it) { void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) { for (int led = it.size(); led-- > 0;) { - it[led].set(light::ESPColor::BLACK); + it[led].set(COLOR_BLACK); } } -void AdalightLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) { +void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { const uint32_t now = millis(); if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) { @@ -130,7 +130,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL for (int led = 0; led < accepted_led_count; led++, led_data += 3) { auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]); - it[led].set(light::ESPColor(led_data[0], led_data[1], led_data[2], white)); + it[led].set(Color(led_data[0], led_data[1], led_data[2], white)); } return CONSUMED; diff --git a/esphome/components/adalight/adalight_light_effect.h b/esphome/components/adalight/adalight_light_effect.h index 4f77394ebcaf..c1df55659be7 100644 --- a/esphome/components/adalight/adalight_light_effect.h +++ b/esphome/components/adalight/adalight_light_effect.h @@ -16,7 +16,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U public: void start() override; void stop() override; - void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override; + void apply(light::AddressableLight &it, const Color ¤t_color) override; protected: enum Frame { diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 63db7aee2ecd..f70ffa952087 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 9b1f4521318c..c6e2141725cd 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -16,7 +16,9 @@ void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuati void ADCSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); +#ifndef USE_ADC_SENSOR_VCC GPIOPin(this->pin_, INPUT).setup(); +#endif #ifdef ARDUINO_ARCH_ESP32 analogSetPinAttenuation(this->pin_, this->attenuation_); diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 6a274f04af37..2e36c6179a6c 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -2,36 +2,51 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor, voltage_sampler -from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_PIN, ICON_FLASH, UNIT_VOLT +from esphome.const import ( + CONF_ATTENUATION, + CONF_ID, + CONF_PIN, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_VOLT, +) -AUTO_LOAD = ['voltage_sampler'] +AUTO_LOAD = ["voltage_sampler"] ATTENUATION_MODES = { - '0db': cg.global_ns.ADC_0db, - '2.5db': cg.global_ns.ADC_2_5db, - '6db': cg.global_ns.ADC_6db, - '11db': cg.global_ns.ADC_11db, + "0db": cg.global_ns.ADC_0db, + "2.5db": cg.global_ns.ADC_2_5db, + "6db": cg.global_ns.ADC_6db, + "11db": cg.global_ns.ADC_11db, } def validate_adc_pin(value): vcc = str(value).upper() - if vcc == 'VCC': + if vcc == "VCC": return cv.only_on_esp8266(vcc) return pins.analog_pin(value) -adc_ns = cg.esphome_ns.namespace('adc') -ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent, - voltage_sampler.VoltageSampler) +adc_ns = cg.esphome_ns.namespace("adc") +ADCSensor = adc_ns.class_( + "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({ - cv.GenerateID(): cv.declare_id(ADCSensor), - cv.Required(CONF_PIN): validate_adc_pin, - cv.SplitDefault(CONF_ATTENUATION, esp32='0db'): - cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)), -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE) + .extend( + { + cv.GenerateID(): cv.declare_id(ADCSensor), + cv.Required(CONF_PIN): validate_adc_pin, + cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( + cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) + ), + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): @@ -39,8 +54,8 @@ def to_code(config): yield cg.register_component(var, config) yield sensor.register_sensor(var, config) - if config[CONF_PIN] == 'VCC': - cg.add_define('USE_ADC_SENSOR_VCC') + if config[CONF_PIN] == "VCC": + cg.add_define("USE_ADC_SENSOR_VCC") else: cg.add(var.set_pin(config[CONF_PIN])) diff --git a/esphome/components/addressable_light/__init__.py b/esphome/components/addressable_light/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/addressable_light/addressable_light_display.cpp b/esphome/components/addressable_light/addressable_light_display.cpp new file mode 100644 index 000000000000..2e94e9e0822c --- /dev/null +++ b/esphome/components/addressable_light/addressable_light_display.cpp @@ -0,0 +1,67 @@ +#include "addressable_light_display.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace addressable_light { + +static const char* TAG = "addressable_light.display"; + +int AddressableLightDisplay::get_width_internal() { return this->width_; } +int AddressableLightDisplay::get_height_internal() { return this->height_; } + +void AddressableLightDisplay::setup() { + this->addressable_light_buffer_.resize(this->width_ * this->height_, {0, 0, 0, 0}); +} + +void AddressableLightDisplay::update() { + if (!this->enabled_) + return; + + this->do_update_(); + this->display(); +} + +void AddressableLightDisplay::display() { + bool dirty = false; + uint8_t old_r, old_g, old_b, old_w; + Color* c; + + for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) { + c = &(this->addressable_light_buffer_[offset]); + + light::ESPColorView pixel = (*this->light_)[offset]; + + // Track the original values for the pixel view. If it has changed updating, then + // we trigger a redraw. Avoiding redraws == avoiding flicker! + old_r = pixel.get_red(); + old_g = pixel.get_green(); + old_b = pixel.get_blue(); + old_w = pixel.get_white(); + + pixel.set_rgbw(c->r, c->g, c->b, c->w); + + // If the actual value of the pixel changed, then schedule a redraw. + if (pixel.get_red() != old_r || pixel.get_green() != old_g || pixel.get_blue() != old_b || + pixel.get_white() != old_w) { + dirty = true; + } + } + + if (dirty) { + this->light_->schedule_show(); + } +} + +void HOT AddressableLightDisplay::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) + return; + + if (this->pixel_mapper_f_.has_value()) { + // Params are passed by reference, so they may be modified in call. + this->addressable_light_buffer_[(*this->pixel_mapper_f_)(x, y)] = color; + } else { + this->addressable_light_buffer_[y * this->get_width_internal() + x] = color; + } +} +} // namespace addressable_light +} // namespace esphome diff --git a/esphome/components/addressable_light/addressable_light_display.h b/esphome/components/addressable_light/addressable_light_display.h new file mode 100644 index 000000000000..163faf27b077 --- /dev/null +++ b/esphome/components/addressable_light/addressable_light_display.h @@ -0,0 +1,59 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/color.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/components/light/addressable_light.h" + +namespace esphome { +namespace addressable_light { + +class AddressableLightDisplay : public display::DisplayBuffer, public PollingComponent { + public: + light::AddressableLight *get_light() const { return this->light_; } + + void set_width(int32_t width) { width_ = width; } + void set_height(int32_t height) { height_ = height; } + void set_light(light::LightState *state) { + light_state_ = state; + light_ = static_cast(state->get_output()); + } + void set_enabled(bool enabled) { + if (light_state_) { + if (enabled_ && !enabled) { // enabled -> disabled + // - Tell the parent light to refresh, effectively wiping the display. Also + // restores the previous effect (if any). + light_state_->make_call().set_effect(this->last_effect_).perform(); + + } else if (!enabled_ && enabled) { // disabled -> enabled + // - Save the current effect. + this->last_effect_ = light_state_->get_effect_name(); + // - Disable any current effect. + light_state_->make_call().set_effect(0).perform(); + } + } + enabled_ = enabled; + } + bool get_enabled() { return enabled_; } + + void set_pixel_mapper(std::function &&pixel_mapper_f) { this->pixel_mapper_f_ = pixel_mapper_f; } + void setup() override; + void display(); + + protected: + int get_width_internal() override; + int get_height_internal() override; + void draw_absolute_pixel_internal(int x, int y, Color color) override; + void update() override; + + light::LightState *light_state_; + light::AddressableLight *light_; + bool enabled_{true}; + int32_t width_; + int32_t height_; + std::vector addressable_light_buffer_; + optional last_effect_; + optional> pixel_mapper_f_; +}; +} // namespace addressable_light +} // namespace esphome diff --git a/esphome/components/addressable_light/display.py b/esphome/components/addressable_light/display.py new file mode 100644 index 000000000000..e5d3ca3034de --- /dev/null +++ b/esphome/components/addressable_light/display.py @@ -0,0 +1,63 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import display, light +from esphome.const import ( + CONF_ID, + CONF_LAMBDA, + CONF_PAGES, + CONF_ADDRESSABLE_LIGHT_ID, + CONF_HEIGHT, + CONF_WIDTH, + CONF_UPDATE_INTERVAL, + CONF_PIXEL_MAPPER, +) + +CODEOWNERS = ["@justfalter"] + +addressable_light_ns = cg.esphome_ns.namespace("addressable_light") +AddressableLightDisplay = addressable_light_ns.class_( + "AddressableLightDisplay", display.DisplayBuffer, cg.PollingComponent +) + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(AddressableLightDisplay), + cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id( + light.AddressableLightState + ), + cv.Required(CONF_WIDTH): cv.positive_int, + cv.Required(CONF_HEIGHT): cv.positive_int, + cv.Optional( + CONF_UPDATE_INTERVAL, default="16ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_PIXEL_MAPPER): cv.returning_lambda, + } + ), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + wrapped_light = yield cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID]) + cg.add(var.set_width(config[CONF_WIDTH])) + cg.add(var.set_height(config[CONF_HEIGHT])) + cg.add(var.set_light(wrapped_light)) + + yield cg.register_component(var, config) + yield display.register_display(var, config) + + if CONF_PIXEL_MAPPER in config: + pixel_mapper_template_ = yield cg.process_lambda( + config[CONF_PIXEL_MAPPER], + [(int, "x"), (int, "y")], + return_type=cg.int_, + ) + cg.add(var.set_pixel_mapper(pixel_mapper_template_)) + + if CONF_LAMBDA in config: + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ade7953/sensor.py b/esphome/components/ade7953/sensor.py index d29e2dd13ed0..6bc9917806e6 100644 --- a/esphome/components/ade7953/sensor.py +++ b/esphome/components/ade7953/sensor.py @@ -2,29 +2,54 @@ import esphome.config_validation as cv from esphome.components import sensor, i2c from esphome import pins -from esphome.const import CONF_ID, CONF_VOLTAGE, \ - UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT +from esphome.const import ( + CONF_ID, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -ade7953_ns = cg.esphome_ns.namespace('ade7953') -ADE7953 = ade7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice) +ade7953_ns = cg.esphome_ns.namespace("ade7953") +ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice) -CONF_IRQ_PIN = 'irq_pin' -CONF_CURRENT_A = 'current_a' -CONF_CURRENT_B = 'current_b' -CONF_ACTIVE_POWER_A = 'active_power_a' -CONF_ACTIVE_POWER_B = 'active_power_b' +CONF_IRQ_PIN = "irq_pin" +CONF_CURRENT_A = "current_a" +CONF_CURRENT_B = "current_b" +CONF_ACTIVE_POWER_A = "active_power_a" +CONF_ACTIVE_POWER_B = "active_power_b" -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(ADE7953), - cv.Optional(CONF_IRQ_PIN): pins.input_pin, - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), - cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), - cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), - cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), - cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ADE7953), + cv.Optional(CONF_IRQ_PIN): pins.input_pin, + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_CURRENT_A): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_CURRENT_B): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x38)) +) def to_code(config): @@ -35,10 +60,15 @@ def to_code(config): if CONF_IRQ_PIN in config: cg.add(var.set_irq_pin(config[CONF_IRQ_PIN])) - for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A, - CONF_ACTIVE_POWER_B]: + for key in [ + CONF_VOLTAGE, + CONF_CURRENT_A, + CONF_CURRENT_B, + CONF_ACTIVE_POWER_A, + CONF_ACTIVE_POWER_B, + ]: if key not in config: continue conf = config[key] sens = yield sensor.new_sensor(conf) - cg.add(getattr(var, f'set_{key}_sensor')(sens)) + cg.add(getattr(var, f"set_{key}_sensor")(sens)) diff --git a/esphome/components/ads1115/__init__.py b/esphome/components/ads1115/__init__.py index a41a521ba791..32ab17db314e 100644 --- a/esphome/components/ads1115/__init__.py +++ b/esphome/components/ads1115/__init__.py @@ -3,18 +3,24 @@ from esphome.components import i2c from esphome.const import CONF_ID -DEPENDENCIES = ['i2c'] -AUTO_LOAD = ['sensor', 'voltage_sampler'] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensor", "voltage_sampler"] MULTI_CONF = True -ads1115_ns = cg.esphome_ns.namespace('ads1115') -ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice) +ads1115_ns = cg.esphome_ns.namespace("ads1115") +ADS1115Component = ads1115_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice) -CONF_CONTINUOUS_MODE = 'continuous_mode' -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(ADS1115Component), - cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None)) +CONF_CONTINUOUS_MODE = "continuous_mode" +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ADS1115Component), + cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(None)) +) def to_code(config): diff --git a/esphome/components/ads1115/sensor.py b/esphome/components/ads1115/sensor.py index 55619b22e917..6eb39bdd1a7d 100644 --- a/esphome/components/ads1115/sensor.py +++ b/esphome/components/ads1115/sensor.py @@ -1,53 +1,67 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, voltage_sampler -from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID +from esphome.const import ( + CONF_GAIN, + CONF_MULTIPLEXER, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_VOLT, + CONF_ID, +) from . import ads1115_ns, ADS1115Component -DEPENDENCIES = ['ads1115'] +DEPENDENCIES = ["ads1115"] -ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer') +ADS1115Multiplexer = ads1115_ns.enum("ADS1115Multiplexer") MUX = { - 'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1, - 'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3, - 'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3, - 'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3, - 'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG, - 'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG, - 'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG, - 'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG, + "A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1, + "A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3, + "A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3, + "A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3, + "A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG, + "A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG, + "A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG, + "A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG, } -ADS1115Gain = ads1115_ns.enum('ADS1115Gain') +ADS1115Gain = ads1115_ns.enum("ADS1115Gain") GAIN = { - '6.144': ADS1115Gain.ADS1115_GAIN_6P144, - '4.096': ADS1115Gain.ADS1115_GAIN_4P096, - '2.048': ADS1115Gain.ADS1115_GAIN_2P048, - '1.024': ADS1115Gain.ADS1115_GAIN_1P024, - '0.512': ADS1115Gain.ADS1115_GAIN_0P512, - '0.256': ADS1115Gain.ADS1115_GAIN_0P256, + "6.144": ADS1115Gain.ADS1115_GAIN_6P144, + "4.096": ADS1115Gain.ADS1115_GAIN_4P096, + "2.048": ADS1115Gain.ADS1115_GAIN_2P048, + "1.024": ADS1115Gain.ADS1115_GAIN_1P024, + "0.512": ADS1115Gain.ADS1115_GAIN_0P512, + "0.256": ADS1115Gain.ADS1115_GAIN_0P256, } def validate_gain(value): if isinstance(value, float): - value = f'{value:0.03f}' + value = f"{value:0.03f}" elif not isinstance(value, str): raise cv.Invalid(f'invalid gain "{value}"') return cv.enum(GAIN)(value) -ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent, - voltage_sampler.VoltageSampler) +ADS1115Sensor = ads1115_ns.class_( + "ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler +) -CONF_ADS1115_ID = 'ads1115_id' -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({ - cv.GenerateID(): cv.declare_id(ADS1115Sensor), - cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component), - cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space='_'), - cv.Required(CONF_GAIN): validate_gain, -}).extend(cv.polling_component_schema('60s')) +CONF_ADS1115_ID = "ads1115_id" +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE) + .extend( + { + cv.GenerateID(): cv.declare_id(ADS1115Sensor), + cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component), + cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"), + cv.Required(CONF_GAIN): validate_gain, + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): diff --git a/esphome/components/aht10/sensor.py b/esphome/components/aht10/sensor.py index 71b0adce79c0..e335934a8edd 100644 --- a/esphome/components/aht10/sensor.py +++ b/esphome/components/aht10/sensor.py @@ -1,19 +1,37 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -aht10_ns = cg.esphome_ns.namespace('aht10') -AHT10Component = aht10_ns.class_('AHT10Component', cg.PollingComponent, i2c.I2CDevice) +aht10_ns = cg.esphome_ns.namespace("aht10") +AHT10Component = aht10_ns.class_("AHT10Component", cg.PollingComponent, i2c.I2CDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(AHT10Component), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 2), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(AHT10Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x38)) +) def to_code(config): diff --git a/esphome/components/am2320/sensor.py b/esphome/components/am2320/sensor.py index d62899663ce4..6d1cc4258157 100644 --- a/esphome/components/am2320/sensor.py +++ b/esphome/components/am2320/sensor.py @@ -1,19 +1,39 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + UNIT_CELSIUS, + ICON_EMPTY, + UNIT_PERCENT, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -am2320_ns = cg.esphome_ns.namespace('am2320') -AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice) +am2320_ns = cg.esphome_ns.namespace("am2320") +AM2320Component = am2320_ns.class_( + "AM2320Component", cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(AM2320Component), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(AM2320Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x5C)) +) def to_code(config): diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index f19dbd694699..fbcb2b4c1fde 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -10,24 +10,28 @@ _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['display'] +DEPENDENCIES = ["display"] MULTI_CONF = True -Animation_ = display.display_ns.class_('Animation') +Animation_ = display.display_ns.class_("Animation") -CONF_RAW_DATA_ID = 'raw_data_id' +CONF_RAW_DATA_ID = "raw_data_id" -ANIMATION_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(Animation_), - cv.Required(CONF_FILE): cv.file_, - cv.Optional(CONF_RESIZE): cv.dimensions, - cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(espImage.IMAGE_TYPE, upper=True), - cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), -}) +ANIMATION_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(Animation_), + cv.Required(CONF_FILE): cv.file_, + cv.Optional(CONF_RESIZE): cv.dimensions, + cv.Optional(CONF_TYPE, default="BINARY"): cv.enum( + espImage.IMAGE_TYPE, upper=True + ), + cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), + } +) CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA) -CODEOWNERS = ['@syndlex'] +CODEOWNERS = ["@syndlex"] def to_code(config): @@ -46,26 +50,28 @@ def to_code(config): width, height = image.size else: if width > 500 or height > 500: - _LOGGER.warning("The image you requested is very big. Please consider using" - " the resize parameter.") + _LOGGER.warning( + "The image you requested is very big. Please consider using" + " the resize parameter." + ) - if config[CONF_TYPE] == 'GRAYSCALE': + if config[CONF_TYPE] == "GRAYSCALE": data = [0 for _ in range(height * width * frames)] pos = 0 for frameIndex in range(frames): image.seek(frameIndex) - frame = image.convert('L', dither=Image.NONE) + frame = image.convert("L", dither=Image.NONE) pixels = list(frame.getdata()) for pix in pixels: data[pos] = pix pos += 1 - elif config[CONF_TYPE] == 'RGB24': + elif config[CONF_TYPE] == "RGB24": data = [0 for _ in range(height * width * 3 * frames)] pos = 0 for frameIndex in range(frames): image.seek(frameIndex) - frame = image.convert('RGB') + frame = image.convert("RGB") pixels = list(frame.getdata()) for pix in pixels: data[pos] = pix[0] @@ -75,12 +81,12 @@ def to_code(config): data[pos] = pix[2] pos += 1 - elif config[CONF_TYPE] == 'BINARY': + elif config[CONF_TYPE] == "BINARY": width8 = ((width + 7) // 8) * 8 data = [0 for _ in range((height * width8 // 8) * frames)] for frameIndex in range(frames): image.seek(frameIndex) - frame = image.convert('1', dither=Image.NONE) + frame = image.convert("1", dither=Image.NONE) for y in range(height): for x in range(width): if frame.getpixel((x, y)): @@ -90,5 +96,11 @@ def to_code(config): rhs = [HexInt(x) for x in data] prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) - cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, frames, - espImage.IMAGE_TYPE[config[CONF_TYPE]]) + cg.new_Pvariable( + config[CONF_ID], + prog_arr, + width, + height, + frames, + espImage.IMAGE_TYPE[config[CONF_TYPE]], + ) diff --git a/esphome/components/apds9960/__init__.py b/esphome/components/apds9960/__init__.py index 4725c1603230..9346b5ece1af 100644 --- a/esphome/components/apds9960/__init__.py +++ b/esphome/components/apds9960/__init__.py @@ -3,18 +3,24 @@ from esphome.components import i2c from esphome.const import CONF_ID -DEPENDENCIES = ['i2c'] -AUTO_LOAD = ['sensor', 'binary_sensor'] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensor", "binary_sensor"] MULTI_CONF = True -CONF_APDS9960_ID = 'apds9960_id' +CONF_APDS9960_ID = "apds9960_id" -apds9960_nds = cg.esphome_ns.namespace('apds9960') -APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice) +apds9960_nds = cg.esphome_ns.namespace("apds9960") +APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(APDS9960), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(APDS9960), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x39)) +) def to_code(config): diff --git a/esphome/components/apds9960/binary_sensor.py b/esphome/components/apds9960/binary_sensor.py index 440451090967..043306138551 100644 --- a/esphome/components/apds9960/binary_sensor.py +++ b/esphome/components/apds9960/binary_sensor.py @@ -4,20 +4,24 @@ from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING from . import APDS9960, CONF_APDS9960_ID -DEPENDENCIES = ['apds9960'] +DEPENDENCIES = ["apds9960"] DIRECTIONS = { - 'UP': 'set_up_direction', - 'DOWN': 'set_down_direction', - 'LEFT': 'set_left_direction', - 'RIGHT': 'set_right_direction', + "UP": "set_up_direction", + "DOWN": "set_down_direction", + "LEFT": "set_left_direction", + "RIGHT": "set_right_direction", } -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), - cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), - cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class, -}) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), + cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), + cv.Optional( + CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING + ): binary_sensor.device_class, + } +) def to_code(config): diff --git a/esphome/components/apds9960/sensor.py b/esphome/components/apds9960/sensor.py index 58087cbe86be..a1ebd9b5c3d5 100644 --- a/esphome/components/apds9960/sensor.py +++ b/esphome/components/apds9960/sensor.py @@ -1,23 +1,27 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import CONF_TYPE, UNIT_PERCENT, ICON_LIGHTBULB +from esphome.const import CONF_TYPE, DEVICE_CLASS_EMPTY, UNIT_PERCENT, ICON_LIGHTBULB from . import APDS9960, CONF_APDS9960_ID -DEPENDENCIES = ['apds9960'] +DEPENDENCIES = ["apds9960"] TYPES = { - 'CLEAR': 'set_clear_channel', - 'RED': 'set_red_channel', - 'GREEN': 'set_green_channel', - 'BLUE': 'set_blue_channel', - 'PROXIMITY': 'set_proximity', + "CLEAR": "set_clear_channel", + "RED": "set_red_channel", + "GREEN": "set_green_channel", + "BLUE": "set_blue_channel", + "PROXIMITY": "set_proximity", } -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({ - cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True), - cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), -}) +CONFIG_SCHEMA = sensor.sensor_schema( + UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY +).extend( + { + cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True), + cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), + } +) def to_code(config): diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 884ae9867a95..d12c01e6149e 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -2,46 +2,69 @@ import esphome.config_validation as cv from esphome import automation from esphome.automation import Condition -from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \ - CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT, \ - CONF_TAG +from esphome.const import ( + CONF_DATA, + CONF_DATA_TEMPLATE, + CONF_ID, + CONF_PASSWORD, + CONF_PORT, + CONF_REBOOT_TIMEOUT, + CONF_SERVICE, + CONF_VARIABLES, + CONF_SERVICES, + CONF_TRIGGER_ID, + CONF_EVENT, + CONF_TAG, +) from esphome.core import coroutine_with_priority -DEPENDENCIES = ['network'] -AUTO_LOAD = ['async_tcp'] -CODEOWNERS = ['@OttoWinter'] +DEPENDENCIES = ["network"] +AUTO_LOAD = ["async_tcp"] +CODEOWNERS = ["@OttoWinter"] -api_ns = cg.esphome_ns.namespace('api') -APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller) -HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action) -APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition) +api_ns = cg.esphome_ns.namespace("api") +APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller) +HomeAssistantServiceCallAction = api_ns.class_( + "HomeAssistantServiceCallAction", automation.Action +) +APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition) -UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger) -ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument') +UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger) +ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument") SERVICE_ARG_NATIVE_TYPES = { - 'bool': bool, - 'int': cg.int32, - 'float': float, - 'string': cg.std_string, - 'bool[]': cg.std_vector.template(bool), - 'int[]': cg.std_vector.template(cg.int32), - 'float[]': cg.std_vector.template(float), - 'string[]': cg.std_vector.template(cg.std_string), + "bool": bool, + "int": cg.int32, + "float": float, + "string": cg.std_string, + "bool[]": cg.std_vector.template(bool), + "int[]": cg.std_vector.template(cg.int32), + "float[]": cg.std_vector.template(float), + "string[]": cg.std_vector.template(cg.std_string), } -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(APIServer), - cv.Optional(CONF_PORT, default=6053): cv.port, - cv.Optional(CONF_PASSWORD, default=''): cv.string_strict, - cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_SERVICES): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), - cv.Required(CONF_SERVICE): cv.valid_name, - cv.Optional(CONF_VARIABLES, default={}): cv.Schema({ - cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True), - }), - }), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(APIServer), + cv.Optional(CONF_PORT, default=6053): cv.port, + cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, + cv.Optional( + CONF_REBOOT_TIMEOUT, default="15min" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_SERVICES): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), + cv.Required(CONF_SERVICE): cv.valid_name, + cv.Optional(CONF_VARIABLES, default={}): cv.Schema( + { + cv.validate_id_name: cv.one_of( + *SERVICE_ARG_NATIVE_TYPES, lower=True + ), + } + ), + } + ), + } +).extend(cv.COMPONENT_SCHEMA) @coroutine_with_priority(40.0) @@ -63,28 +86,36 @@ def to_code(config): func_args.append((native, name)) service_arg_names.append(name) templ = cg.TemplateArguments(*template_args) - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ, - conf[CONF_SERVICE], service_arg_names) + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names + ) cg.add(var.register_user_service(trigger)) yield automation.build_automation(trigger, func_args, conf) - cg.add_define('USE_API') + cg.add_define("USE_API") cg.add_global(api_ns.using) KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)}) -HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.use_id(APIServer), - cv.Required(CONF_SERVICE): cv.templatable(cv.string), - cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, - cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, - cv.Optional(CONF_VARIABLES, default={}): cv.Schema({cv.string: cv.returning_lambda}), -}) - - -@automation.register_action('homeassistant.service', HomeAssistantServiceCallAction, - HOMEASSISTANT_SERVICE_ACTION_SCHEMA) +HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(APIServer), + cv.Required(CONF_SERVICE): cv.templatable(cv.string), + cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, + cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, + cv.Optional(CONF_VARIABLES, default={}): cv.Schema( + {cv.string: cv.returning_lambda} + ), + } +) + + +@automation.register_action( + "homeassistant.service", + HomeAssistantServiceCallAction, + HOMEASSISTANT_SERVICE_ACTION_SCHEMA, +) def homeassistant_service_to_code(config, action_id, template_arg, args): serv = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, serv, False) @@ -104,23 +135,30 @@ def homeassistant_service_to_code(config, action_id, template_arg, args): def validate_homeassistant_event(value): value = cv.string(value) - if not value.startswith('esphome.'): - raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with " - "esphome. For example 'esphome.xyz'") + if not value.startswith("esphome."): + raise cv.Invalid( + "ESPHome can only generate Home Assistant events that begin with " + "esphome. For example 'esphome.xyz'" + ) return value -HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.use_id(APIServer), - cv.Required(CONF_EVENT): validate_homeassistant_event, - cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, - cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, - cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA, -}) +HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(APIServer), + cv.Required(CONF_EVENT): validate_homeassistant_event, + cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, + cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, + cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA, + } +) -@automation.register_action('homeassistant.event', HomeAssistantServiceCallAction, - HOMEASSISTANT_EVENT_ACTION_SCHEMA) +@automation.register_action( + "homeassistant.event", + HomeAssistantServiceCallAction, + HOMEASSISTANT_EVENT_ACTION_SCHEMA, +) def homeassistant_event_to_code(config, action_id, template_arg, args): serv = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, serv, True) @@ -138,23 +176,29 @@ def homeassistant_event_to_code(config, action_id, template_arg, args): yield var -HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value({ - cv.GenerateID(): cv.use_id(APIServer), - cv.Required(CONF_TAG): cv.templatable(cv.string_strict), -}, key=CONF_TAG) +HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(APIServer), + cv.Required(CONF_TAG): cv.templatable(cv.string_strict), + }, + key=CONF_TAG, +) -@automation.register_action('homeassistant.tag_scanned', HomeAssistantServiceCallAction, - HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA) +@automation.register_action( + "homeassistant.tag_scanned", + HomeAssistantServiceCallAction, + HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA, +) def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args): serv = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, serv, True) - cg.add(var.set_service('esphome.tag_scanned')) + cg.add(var.set_service("esphome.tag_scanned")) templ = yield cg.templatable(config[CONF_TAG], args, cg.std_string) - cg.add(var.add_data('tag_id', templ)) + cg.add(var.add_data("tag_id", templ)) yield var -@automation.register_condition('api.connected', APIConnectedCondition, {}) +@automation.register_condition("api.connected", APIConnectedCondition, {}) def api_connected_to_code(config, condition_id, template_arg, args): yield cg.new_Pvariable(condition_id, template_arg) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 271b1d6c5f87..ede2cc620501 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -46,6 +46,7 @@ service APIConnection { // The Home Assistant protocol is structured as a simple // TCP socket with short binary messages encoded in the protocol buffers format // First, a message in this protocol has a specific format: +// * A zero byte. // * VarInt denoting the size of the message object. (type is not part of this) // * VarInt denoting the type of message. // * The message object encoded as a ProtoBuf message @@ -302,6 +303,7 @@ message ListEntitiesFanResponse { bool supports_oscillation = 5; bool supports_speed = 6; bool supports_direction = 7; + int32 supported_speed_count = 8; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -321,8 +323,9 @@ message FanStateResponse { fixed32 key = 1; bool state = 2; bool oscillating = 3; - FanSpeed speed = 4; + FanSpeed speed = 4 [deprecated = true]; FanDirection direction = 5; + int32 speed_level = 6; } message FanCommandRequest { option (id) = 31; @@ -333,12 +336,14 @@ message FanCommandRequest { fixed32 key = 1; bool has_state = 2; bool state = 3; - bool has_speed = 4; - FanSpeed speed = 5; + bool has_speed = 4 [deprecated = true]; + FanSpeed speed = 5 [deprecated = true]; bool has_oscillating = 6; bool oscillating = 7; bool has_direction = 8; FanDirection direction = 9; + bool has_speed_level = 10; + int32 speed_level = 11; } // ==================== LIGHT ==================== @@ -418,6 +423,7 @@ message ListEntitiesSensorResponse { string unit_of_measurement = 6; int32 accuracy_decimals = 7; bool force_update = 8; + string device_class = 9; } message SensorStateResponse { option (id) = 25; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 431be5b4dc99..8098c937810c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -9,6 +9,9 @@ #ifdef USE_HOMEASSISTANT_TIME #include "esphome/components/homeassistant/time/homeassistant_time.h" #endif +#ifdef USE_FAN +#include "esphome/components/fan/fan_helpers.h" +#endif namespace esphome { namespace api { @@ -246,8 +249,10 @@ bool APIConnection::send_fan_state(fan::FanState *fan) { resp.state = fan->state; if (traits.supports_oscillation()) resp.oscillating = fan->oscillating; - if (traits.supports_speed()) - resp.speed = static_cast(fan->speed); + if (traits.supports_speed()) { + resp.speed_level = fan->speed; + resp.speed = static_cast(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count())); + } if (traits.supports_direction()) resp.direction = static_cast(fan->direction); return this->send_fan_state_response(resp); @@ -262,6 +267,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { msg.supports_oscillation = traits.supports_oscillation(); msg.supports_speed = traits.supports_speed(); msg.supports_direction = traits.supports_direction(); + msg.supported_speed_count = traits.supported_speed_count(); return this->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { @@ -269,13 +275,19 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { if (fan == nullptr) return; + auto traits = fan->get_traits(); + auto call = fan->make_call(); if (msg.has_state) call.set_state(msg.state); if (msg.has_oscillating) call.set_oscillating(msg.oscillating); - if (msg.has_speed) - call.set_speed(static_cast(msg.speed)); + if (msg.has_speed_level) { + // Prefer level + call.set_speed(msg.speed_level); + } else if (msg.has_speed) { + call.set_speed(fan::speed_enum_to_level(static_cast(msg.speed), traits.supported_speed_count())); + } if (msg.has_direction) call.set_direction(static_cast(msg.direction)); call.perform(); @@ -382,6 +394,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.unit_of_measurement = sensor->get_unit_of_measurement(); msg.accuracy_decimals = sensor->get_accuracy_decimals(); msg.force_update = sensor->get_force_update(); + msg.device_class = sensor->get_device_class(); return this->send_list_entities_sensor_response(msg); } #endif @@ -589,7 +602,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 3; + resp.api_version_minor = 4; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; this->connection_state_ = ConnectionState::CONNECTED; return resp; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index a7e521c69946..23538b77bfb2 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -774,6 +774,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value this->supports_direction = value.as_bool(); return true; } + case 8: { + this->supported_speed_count = value.as_int32(); + return true; + } default: return false; } @@ -814,6 +818,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(5, this->supports_oscillation); buffer.encode_bool(6, this->supports_speed); buffer.encode_bool(7, this->supports_direction); + buffer.encode_int32(8, this->supported_speed_count); } void ListEntitiesFanResponse::dump_to(std::string &out) const { char buffer[64]; @@ -846,6 +851,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append(" supports_direction: "); out.append(YESNO(this->supports_direction)); out.append("\n"); + + out.append(" supported_speed_count: "); + sprintf(buffer, "%d", this->supported_speed_count); + out.append(buffer); + out.append("\n"); out.append("}"); } bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -866,6 +876,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->direction = value.as_enum(); return true; } + case 6: { + this->speed_level = value.as_int32(); + return true; + } default: return false; } @@ -886,6 +900,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(3, this->oscillating); buffer.encode_enum(4, this->speed); buffer.encode_enum(5, this->direction); + buffer.encode_int32(6, this->speed_level); } void FanStateResponse::dump_to(std::string &out) const { char buffer[64]; @@ -910,6 +925,11 @@ void FanStateResponse::dump_to(std::string &out) const { out.append(" direction: "); out.append(proto_enum_to_string(this->direction)); out.append("\n"); + + out.append(" speed_level: "); + sprintf(buffer, "%d", this->speed_level); + out.append(buffer); + out.append("\n"); out.append("}"); } bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -946,6 +966,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { this->direction = value.as_enum(); return true; } + case 10: { + this->has_speed_level = value.as_bool(); + return true; + } + case 11: { + this->speed_level = value.as_int32(); + return true; + } default: return false; } @@ -970,6 +998,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->oscillating); buffer.encode_bool(8, this->has_direction); buffer.encode_enum(9, this->direction); + buffer.encode_bool(10, this->has_speed_level); + buffer.encode_int32(11, this->speed_level); } void FanCommandRequest::dump_to(std::string &out) const { char buffer[64]; @@ -1010,6 +1040,15 @@ void FanCommandRequest::dump_to(std::string &out) const { out.append(" direction: "); out.append(proto_enum_to_string(this->direction)); out.append("\n"); + + out.append(" has_speed_level: "); + out.append(YESNO(this->has_speed_level)); + out.append("\n"); + + out.append(" speed_level: "); + sprintf(buffer, "%d", this->speed_level); + out.append(buffer); + out.append("\n"); out.append("}"); } bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -1494,6 +1533,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel this->unit_of_measurement = value.as_string(); return true; } + case 9: { + this->device_class = value.as_string(); + return true; + } default: return false; } @@ -1517,6 +1560,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(6, this->unit_of_measurement); buffer.encode_int32(7, this->accuracy_decimals); buffer.encode_bool(8, this->force_update); + buffer.encode_string(9, this->device_class); } void ListEntitiesSensorResponse::dump_to(std::string &out) const { char buffer[64]; @@ -1554,6 +1598,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(" force_update: "); out.append(YESNO(this->force_update)); out.append("\n"); + + out.append(" device_class: "); + out.append("'").append(this->device_class).append("'"); + out.append("\n"); out.append("}"); } bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index b97320b48a66..f70ac74a7953 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -284,6 +284,7 @@ class ListEntitiesFanResponse : public ProtoMessage { bool supports_oscillation{false}; // NOLINT bool supports_speed{false}; // NOLINT bool supports_direction{false}; // NOLINT + int32_t supported_speed_count{0}; // NOLINT void encode(ProtoWriteBuffer buffer) const override; void dump_to(std::string &out) const override; @@ -299,6 +300,7 @@ class FanStateResponse : public ProtoMessage { bool oscillating{false}; // NOLINT enums::FanSpeed speed{}; // NOLINT enums::FanDirection direction{}; // NOLINT + int32_t speed_level{0}; // NOLINT void encode(ProtoWriteBuffer buffer) const override; void dump_to(std::string &out) const override; @@ -317,6 +319,8 @@ class FanCommandRequest : public ProtoMessage { bool oscillating{false}; // NOLINT bool has_direction{false}; // NOLINT enums::FanDirection direction{}; // NOLINT + bool has_speed_level{false}; // NOLINT + int32_t speed_level{0}; // NOLINT void encode(ProtoWriteBuffer buffer) const override; void dump_to(std::string &out) const override; @@ -403,6 +407,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { std::string unit_of_measurement{}; // NOLINT int32_t accuracy_decimals{0}; // NOLINT bool force_update{false}; // NOLINT + std::string device_class{}; // NOLINT void encode(ProtoWriteBuffer buffer) const override; void dump_to(std::string &out) const override; diff --git a/esphome/components/as3935/__init__.py b/esphome/components/as3935/__init__.py index 51958048ca1b..337f0a908168 100644 --- a/esphome/components/as3935/__init__.py +++ b/esphome/components/as3935/__init__.py @@ -1,33 +1,43 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.const import CONF_INDOOR, CONF_WATCHDOG_THRESHOLD, \ - CONF_NOISE_LEVEL, CONF_SPIKE_REJECTION, CONF_LIGHTNING_THRESHOLD, \ - CONF_MASK_DISTURBER, CONF_DIV_RATIO, CONF_CAPACITANCE +from esphome.const import ( + CONF_INDOOR, + CONF_WATCHDOG_THRESHOLD, + CONF_NOISE_LEVEL, + CONF_SPIKE_REJECTION, + CONF_LIGHTNING_THRESHOLD, + CONF_MASK_DISTURBER, + CONF_DIV_RATIO, + CONF_CAPACITANCE, +) from esphome.core import coroutine -AUTO_LOAD = ['sensor', 'binary_sensor'] +AUTO_LOAD = ["sensor", "binary_sensor"] MULTI_CONF = True -CONF_AS3935_ID = 'as3935_id' - -as3935_ns = cg.esphome_ns.namespace('as3935') -AS3935 = as3935_ns.class_('AS3935Component', cg.Component) - -CONF_IRQ_PIN = 'irq_pin' -AS3935_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(AS3935), - cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema, - - cv.Optional(CONF_INDOOR, default=True): cv.boolean, - cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7), - cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10), - cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11), - cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True), - cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean, - cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True), - cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15), -}) +CONF_AS3935_ID = "as3935_id" + +as3935_ns = cg.esphome_ns.namespace("as3935") +AS3935 = as3935_ns.class_("AS3935Component", cg.Component) + +CONF_IRQ_PIN = "irq_pin" +AS3935_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(AS3935), + cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_INDOOR, default=True): cv.boolean, + cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7), + cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10), + cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11), + cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of( + 1, 5, 9, 16, int=True + ), + cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean, + cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True), + cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15), + } +) @coroutine diff --git a/esphome/components/as3935/binary_sensor.py b/esphome/components/as3935/binary_sensor.py index 3748c3484a7b..85ba05286982 100644 --- a/esphome/components/as3935/binary_sensor.py +++ b/esphome/components/as3935/binary_sensor.py @@ -3,11 +3,13 @@ from esphome.components import binary_sensor from . import AS3935, CONF_AS3935_ID -DEPENDENCIES = ['as3935'] +DEPENDENCIES = ["as3935"] -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), -}) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), + } +) def to_code(config): diff --git a/esphome/components/as3935/sensor.py b/esphome/components/as3935/sensor.py index 016df8f2a14e..64eeea7b0448 100644 --- a/esphome/components/as3935/sensor.py +++ b/esphome/components/as3935/sensor.py @@ -1,19 +1,30 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import CONF_DISTANCE, CONF_LIGHTNING_ENERGY, \ - UNIT_KILOMETER, UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH +from esphome.const import ( + CONF_DISTANCE, + CONF_LIGHTNING_ENERGY, + DEVICE_CLASS_EMPTY, + UNIT_KILOMETER, + UNIT_EMPTY, + ICON_SIGNAL_DISTANCE_VARIANT, + ICON_FLASH, +) from . import AS3935, CONF_AS3935_ID -DEPENDENCIES = ['as3935'] +DEPENDENCIES = ["as3935"] -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), - cv.Optional(CONF_DISTANCE): - sensor.sensor_schema(UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1), - cv.Optional(CONF_LIGHTNING_ENERGY): - sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 1), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), + cv.Optional(CONF_DISTANCE): sensor.sensor_schema( + UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema( + UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/as3935_i2c/__init__.py b/esphome/components/as3935_i2c/__init__.py index e22937ab818d..277ccd84d3bd 100644 --- a/esphome/components/as3935_i2c/__init__.py +++ b/esphome/components/as3935_i2c/__init__.py @@ -3,15 +3,21 @@ from esphome.components import as3935, i2c from esphome.const import CONF_ID -AUTO_LOAD = ['as3935'] -DEPENDENCIES = ['i2c'] +AUTO_LOAD = ["as3935"] +DEPENDENCIES = ["i2c"] -as3935_i2c_ns = cg.esphome_ns.namespace('as3935_i2c') -I2CAS3935 = as3935_i2c_ns.class_('I2CAS3935Component', as3935.AS3935, i2c.I2CDevice) +as3935_i2c_ns = cg.esphome_ns.namespace("as3935_i2c") +I2CAS3935 = as3935_i2c_ns.class_("I2CAS3935Component", as3935.AS3935, i2c.I2CDevice) -CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(I2CAS3935), -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x03))) +CONFIG_SCHEMA = cv.All( + as3935.AS3935_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(I2CAS3935), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x03)) +) def to_code(config): diff --git a/esphome/components/as3935_spi/__init__.py b/esphome/components/as3935_spi/__init__.py index 30c2240c27c9..b921444b8bfa 100644 --- a/esphome/components/as3935_spi/__init__.py +++ b/esphome/components/as3935_spi/__init__.py @@ -3,15 +3,21 @@ from esphome.components import as3935, spi from esphome.const import CONF_ID -AUTO_LOAD = ['as3935'] -DEPENDENCIES = ['spi'] +AUTO_LOAD = ["as3935"] +DEPENDENCIES = ["spi"] -as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi') -SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice) +as3935_spi_ns = cg.esphome_ns.namespace("as3935_spi") +SPIAS3935 = as3935_spi_ns.class_("SPIAS3935Component", as3935.AS3935, spi.SPIDevice) -CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SPIAS3935), -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True))) +CONFIG_SCHEMA = cv.All( + as3935.AS3935_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SPIAS3935), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema(cs_pin_required=True)) +) def to_code(config): diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index b2307d5a7b5f..b07db9ed7c59 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -2,14 +2,14 @@ import esphome.codegen as cg from esphome.core import CORE, coroutine_with_priority -CODEOWNERS = ['@OttoWinter'] +CODEOWNERS = ["@OttoWinter"] @coroutine_with_priority(200.0) def to_code(config): if CORE.is_esp32: # https://github.com/OttoWinter/AsyncTCP/blob/master/library.json - cg.add_library('AsyncTCP-esphome', '1.1.1') + cg.add_library("AsyncTCP-esphome", "1.1.1") elif CORE.is_esp8266: # https://github.com/OttoWinter/ESPAsyncTCP - cg.add_library('ESPAsyncTCP-esphome', '1.2.3') + cg.add_library("ESPAsyncTCP-esphome", "1.2.3") diff --git a/esphome/components/atc_mithermometer/sensor.py b/esphome/components/atc_mithermometer/sensor.py index 550cd8d0f865..51891b33cbf7 100644 --- a/esphome/components/atc_mithermometer/sensor.py +++ b/esphome/components/atc_mithermometer/sensor.py @@ -1,27 +1,54 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_BATTERY_LEVEL, CONF_BATTERY_VOLTAGE, CONF_MAC_ADDRESS, \ - CONF_HUMIDITY, CONF_TEMPERATURE, CONF_ID, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, \ - ICON_BATTERY, ICON_THERMOMETER, ICON_WATER_PERCENT - -CODEOWNERS = ['@ahpohl'] - -DEPENDENCIES = ['esp32_ble_tracker'] - -atc_mithermometer_ns = cg.esphome_ns.namespace('atc_mithermometer') -ATCMiThermometer = atc_mithermometer_ns.class_('ATCMiThermometer', - esp32_ble_tracker.ESPBTDeviceListener, - cg.Component) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(ATCMiThermometer), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), - cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_BATTERY, 3), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_BATTERY_VOLTAGE, + CONF_MAC_ADDRESS, + CONF_HUMIDITY, + CONF_TEMPERATURE, + CONF_ID, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, + UNIT_VOLT, +) + +CODEOWNERS = ["@ahpohl"] + +DEPENDENCIES = ["esp32_ble_tracker"] + +atc_mithermometer_ns = cg.esphome_ns.namespace("atc_mithermometer") +ATCMiThermometer = atc_mithermometer_ns.class_( + "ATCMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ATCMiThermometer), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index f27fd3126a92..d0813cfa5276 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -1,61 +1,106 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi -from esphome.const import \ - CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_POWER_FACTOR, CONF_FREQUENCY, \ - ICON_FLASH, ICON_LIGHTBULB, ICON_CURRENT_AC, ICON_THERMOMETER, \ - UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, UNIT_CELSIUS, UNIT_VOLT_AMPS_REACTIVE +from esphome.const import ( + CONF_ID, + CONF_VOLTAGE, + CONF_CURRENT, + CONF_POWER, + CONF_POWER_FACTOR, + CONF_FREQUENCY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + ICON_LIGHTBULB, + ICON_CURRENT_AC, + UNIT_HERTZ, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, + UNIT_EMPTY, + UNIT_CELSIUS, + UNIT_VOLT_AMPS_REACTIVE, +) -CONF_PHASE_A = 'phase_a' -CONF_PHASE_B = 'phase_b' -CONF_PHASE_C = 'phase_c' +CONF_PHASE_A = "phase_a" +CONF_PHASE_B = "phase_b" +CONF_PHASE_C = "phase_c" -CONF_REACTIVE_POWER = 'reactive_power' -CONF_LINE_FREQUENCY = 'line_frequency' -CONF_CHIP_TEMPERATURE = 'chip_temperature' -CONF_GAIN_PGA = 'gain_pga' -CONF_CURRENT_PHASES = 'current_phases' -CONF_GAIN_VOLTAGE = 'gain_voltage' -CONF_GAIN_CT = 'gain_ct' +CONF_REACTIVE_POWER = "reactive_power" +CONF_LINE_FREQUENCY = "line_frequency" +CONF_CHIP_TEMPERATURE = "chip_temperature" +CONF_GAIN_PGA = "gain_pga" +CONF_CURRENT_PHASES = "current_phases" +CONF_GAIN_VOLTAGE = "gain_voltage" +CONF_GAIN_CT = "gain_ct" LINE_FREQS = { - '50HZ': 50, - '60HZ': 60, + "50HZ": 50, + "60HZ": 60, } CURRENT_PHASES = { - '2': 2, - '3': 3, + "2": 2, + "3": 3, } PGA_GAINS = { - '1X': 0x0, - '2X': 0x15, - '4X': 0x2A, + "1X": 0x0, + "2X": 0x15, + "4X": 0x2A, } -atm90e32_ns = cg.esphome_ns.namespace('atm90e32') -ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent, spi.SPIDevice) +atm90e32_ns = cg.esphome_ns.namespace("atm90e32") +ATM90E32Component = atm90e32_ns.class_( + "ATM90E32Component", cg.PollingComponent, spi.SPIDevice +) -ATM90E32_PHASE_SCHEMA = cv.Schema({ - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), - cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 2), - cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2), - cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(UNIT_VOLT_AMPS_REACTIVE, - ICON_LIGHTBULB, 2), - cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2), - cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, - cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, -}) +ATM90E32_PHASE_SCHEMA = cv.Schema( + { + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER + ), + cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( + UNIT_VOLT_AMPS_REACTIVE, ICON_LIGHTBULB, 2, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_POWER_FACTOR + ), + cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, + cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, + } +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(ATM90E32Component), - cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA, - cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA, - cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA, - cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1), - cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), - cv.Optional(CONF_CURRENT_PHASES, default='3'): cv.enum(CURRENT_PHASES, upper=True), - cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True), -}).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema()) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ATM90E32Component), + cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA, + cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA, + cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA, + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), + cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum( + CURRENT_PHASES, upper=True + ), + cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) +) def to_code(config): diff --git a/esphome/components/bang_bang/__init__.py b/esphome/components/bang_bang/__init__.py index 6f14e1003304..71a87b6ae508 100644 --- a/esphome/components/bang_bang/__init__.py +++ b/esphome/components/bang_bang/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@OttoWinter'] +CODEOWNERS = ["@OttoWinter"] diff --git a/esphome/components/bang_bang/climate.py b/esphome/components/bang_bang/climate.py index 4ef811c55d45..e0d75a6a1ebd 100644 --- a/esphome/components/bang_bang/climate.py +++ b/esphome/components/bang_bang/climate.py @@ -2,27 +2,41 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import climate, sensor -from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \ - CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION, \ - CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR - -bang_bang_ns = cg.esphome_ns.namespace('bang_bang') -BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate, cg.Component) -BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig') - -CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(BangBangClimate), - cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), - cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, - cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, - cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_AWAY_CONFIG): cv.Schema({ - cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, - cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, - }), -}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION)) +from esphome.const import ( + CONF_AWAY_CONFIG, + CONF_COOL_ACTION, + CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, + CONF_DEFAULT_TARGET_TEMPERATURE_LOW, + CONF_HEAT_ACTION, + CONF_ID, + CONF_IDLE_ACTION, + CONF_SENSOR, +) + +bang_bang_ns = cg.esphome_ns.namespace("bang_bang") +BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Component) +BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig") + +CONFIG_SCHEMA = cv.All( + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BangBangClimate), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, + cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, + cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_AWAY_CONFIG): cv.Schema( + { + cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, + cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, + } + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION), +) def to_code(config): @@ -35,23 +49,29 @@ def to_code(config): normal_config = BangBangClimateTargetTempConfig( config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], - config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] + config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], ) cg.add(var.set_normal_config(normal_config)) - yield automation.build_automation(var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]) + yield automation.build_automation( + var.get_idle_trigger(), [], config[CONF_IDLE_ACTION] + ) if CONF_COOL_ACTION in config: - yield automation.build_automation(var.get_cool_trigger(), [], config[CONF_COOL_ACTION]) + yield automation.build_automation( + var.get_cool_trigger(), [], config[CONF_COOL_ACTION] + ) cg.add(var.set_supports_cool(True)) if CONF_HEAT_ACTION in config: - yield automation.build_automation(var.get_heat_trigger(), [], config[CONF_HEAT_ACTION]) + yield automation.build_automation( + var.get_heat_trigger(), [], config[CONF_HEAT_ACTION] + ) cg.add(var.set_supports_heat(True)) if CONF_AWAY_CONFIG in config: away = config[CONF_AWAY_CONFIG] away_config = BangBangClimateTargetTempConfig( away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], - away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] + away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], ) cg.add(var.set_away_config(away_config)) diff --git a/esphome/components/bh1750/sensor.py b/esphome/components/bh1750/sensor.py index 54735616d5a7..9ad1b7eadb8a 100644 --- a/esphome/components/bh1750/sensor.py +++ b/esphome/components/bh1750/sensor.py @@ -1,26 +1,45 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, CONF_RESOLUTION, UNIT_LUX, ICON_BRIGHTNESS_5 +from esphome.const import ( + CONF_ID, + CONF_RESOLUTION, + DEVICE_CLASS_ILLUMINANCE, + ICON_EMPTY, + UNIT_LUX, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -bh1750_ns = cg.esphome_ns.namespace('bh1750') -BH1750Resolution = bh1750_ns.enum('BH1750Resolution') +bh1750_ns = cg.esphome_ns.namespace("bh1750") +BH1750Resolution = bh1750_ns.enum("BH1750Resolution") BH1750_RESOLUTIONS = { 4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX, 1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX, 0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX, } -BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice) +BH1750Sensor = bh1750_ns.class_( + "BH1750Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) -CONF_MEASUREMENT_TIME = 'measurement_time' -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({ - cv.GenerateID(): cv.declare_id(BH1750Sensor), - cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True), - cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(min=31, max=254), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23)) +CONF_MEASUREMENT_TIME = "measurement_time" +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE) + .extend( + { + cv.GenerateID(): cv.declare_id(BH1750Sensor), + cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum( + BH1750_RESOLUTIONS, float=True + ), + cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range( + min=31, max=254 + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x23)) +) def to_code(config): diff --git a/esphome/components/binary/__init__.py b/esphome/components/binary/__init__.py index 7a8031df54d6..3f6a45d28a24 100644 --- a/esphome/components/binary/__init__.py +++ b/esphome/components/binary/__init__.py @@ -1,3 +1,3 @@ import esphome.codegen as cg -binary_ns = cg.esphome_ns.namespace('binary') +binary_ns = cg.esphome_ns.namespace("binary") diff --git a/esphome/components/binary/fan/__init__.py b/esphome/components/binary/fan/__init__.py index 6969c1dbbfda..88dc50623516 100644 --- a/esphome/components/binary/fan/__init__.py +++ b/esphome/components/binary/fan/__init__.py @@ -1,18 +1,24 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import fan, output -from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, \ - CONF_OUTPUT, CONF_OUTPUT_ID +from esphome.const import ( + CONF_DIRECTION_OUTPUT, + CONF_OSCILLATION_OUTPUT, + CONF_OUTPUT, + CONF_OUTPUT_ID, +) from .. import binary_ns -BinaryFan = binary_ns.class_('BinaryFan', cg.Component) +BinaryFan = binary_ns.class_("BinaryFan", cg.Component) -CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan), - cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), - cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), - cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan), + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/binary/fan/binary_fan.cpp b/esphome/components/binary/fan/binary_fan.cpp index 5fd1867e7f84..fa6ddc249f9c 100644 --- a/esphome/components/binary/fan/binary_fan.cpp +++ b/esphome/components/binary/fan/binary_fan.cpp @@ -16,7 +16,7 @@ void binary::BinaryFan::dump_config() { } } void BinaryFan::setup() { - auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr); + auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0); this->fan_->set_traits(traits); this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); } diff --git a/esphome/components/binary/light/__init__.py b/esphome/components/binary/light/__init__.py index 6167ae239fdd..7e62f4705f19 100644 --- a/esphome/components/binary/light/__init__.py +++ b/esphome/components/binary/light/__init__.py @@ -4,12 +4,14 @@ from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT from .. import binary_ns -BinaryLightOutput = binary_ns.class_('BinaryLightOutput', light.LightOutput) +BinaryLightOutput = binary_ns.class_("BinaryLightOutput", light.LightOutput) -CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput), - cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), -}) +CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput), + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + } +) def to_code(config): diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 753010310c5e..56e6b7dc971a 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -3,131 +3,214 @@ from esphome import automation, core from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt -from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \ - CONF_ID, CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERTED, \ - CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_ON_CLICK, \ - CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \ - CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_FILTERS, + CONF_ID, + CONF_INTERNAL, + CONF_INVALID_COOLDOWN, + CONF_INVERTED, + CONF_MAX_LENGTH, + CONF_MIN_LENGTH, + CONF_ON_CLICK, + CONF_ON_DOUBLE_CLICK, + CONF_ON_MULTI_CLICK, + CONF_ON_PRESS, + CONF_ON_RELEASE, + CONF_ON_STATE, + CONF_STATE, + CONF_TIMING, + CONF_TRIGGER_ID, + CONF_FOR, + CONF_NAME, + CONF_MQTT_ID, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_BATTERY_CHARGING, + DEVICE_CLASS_COLD, + DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_DOOR, + DEVICE_CLASS_GARAGE_DOOR, + DEVICE_CLASS_GAS, + DEVICE_CLASS_HEAT, + DEVICE_CLASS_LIGHT, + DEVICE_CLASS_LOCK, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_MOVING, + DEVICE_CLASS_OCCUPANCY, + DEVICE_CLASS_OPENING, + DEVICE_CLASS_PLUG, + DEVICE_CLASS_POWER, + DEVICE_CLASS_PRESENCE, + DEVICE_CLASS_PROBLEM, + DEVICE_CLASS_SAFETY, + DEVICE_CLASS_SMOKE, + DEVICE_CLASS_SOUND, + DEVICE_CLASS_VIBRATION, + DEVICE_CLASS_WINDOW, +) from esphome.core import CORE, coroutine, coroutine_with_priority from esphome.util import Registry -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] DEVICE_CLASSES = [ - '', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas', - 'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy', - 'opening', 'plug', 'power', 'presence', 'problem', 'safety', 'smoke', - 'sound', 'vibration', 'window' + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_BATTERY_CHARGING, + DEVICE_CLASS_COLD, + DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_DOOR, + DEVICE_CLASS_GARAGE_DOOR, + DEVICE_CLASS_GAS, + DEVICE_CLASS_HEAT, + DEVICE_CLASS_LIGHT, + DEVICE_CLASS_LOCK, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_MOVING, + DEVICE_CLASS_OCCUPANCY, + DEVICE_CLASS_OPENING, + DEVICE_CLASS_PLUG, + DEVICE_CLASS_POWER, + DEVICE_CLASS_PRESENCE, + DEVICE_CLASS_PROBLEM, + DEVICE_CLASS_SAFETY, + DEVICE_CLASS_SMOKE, + DEVICE_CLASS_SOUND, + DEVICE_CLASS_VIBRATION, + DEVICE_CLASS_WINDOW, ] IS_PLATFORM_COMPONENT = True -binary_sensor_ns = cg.esphome_ns.namespace('binary_sensor') -BinarySensor = binary_sensor_ns.class_('BinarySensor', cg.Nameable) -BinarySensorInitiallyOff = binary_sensor_ns.class_('BinarySensorInitiallyOff', BinarySensor) -BinarySensorPtr = BinarySensor.operator('ptr') +binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") +BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable) +BinarySensorInitiallyOff = binary_sensor_ns.class_( + "BinarySensorInitiallyOff", BinarySensor +) +BinarySensorPtr = BinarySensor.operator("ptr") # Triggers -PressTrigger = binary_sensor_ns.class_('PressTrigger', automation.Trigger.template()) -ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', automation.Trigger.template()) -ClickTrigger = binary_sensor_ns.class_('ClickTrigger', automation.Trigger.template()) -DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', automation.Trigger.template()) -MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', automation.Trigger.template(), - cg.Component) -MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent') -StateTrigger = binary_sensor_ns.class_('StateTrigger', automation.Trigger.template(bool)) -BinarySensorPublishAction = binary_sensor_ns.class_('BinarySensorPublishAction', automation.Action) +PressTrigger = binary_sensor_ns.class_("PressTrigger", automation.Trigger.template()) +ReleaseTrigger = binary_sensor_ns.class_( + "ReleaseTrigger", automation.Trigger.template() +) +ClickTrigger = binary_sensor_ns.class_("ClickTrigger", automation.Trigger.template()) +DoubleClickTrigger = binary_sensor_ns.class_( + "DoubleClickTrigger", automation.Trigger.template() +) +MultiClickTrigger = binary_sensor_ns.class_( + "MultiClickTrigger", automation.Trigger.template(), cg.Component +) +MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent") +StateTrigger = binary_sensor_ns.class_( + "StateTrigger", automation.Trigger.template(bool) +) +BinarySensorPublishAction = binary_sensor_ns.class_( + "BinarySensorPublishAction", automation.Action +) # Condition -BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition) +BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition) # Filters -Filter = binary_sensor_ns.class_('Filter') -DelayedOnOffFilter = binary_sensor_ns.class_('DelayedOnOffFilter', Filter, cg.Component) -DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, cg.Component) -DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, cg.Component) -InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter) -LambdaFilter = binary_sensor_ns.class_('LambdaFilter', Filter) +Filter = binary_sensor_ns.class_("Filter") +DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component) +DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component) +DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component) +InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter) +LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) FILTER_REGISTRY = Registry() -validate_filters = cv.validate_registry('filter', FILTER_REGISTRY) +validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) -@FILTER_REGISTRY.register('invert', InvertFilter, {}) +@FILTER_REGISTRY.register("invert", InvertFilter, {}) def invert_filter_to_code(config, filter_id): yield cg.new_Pvariable(filter_id) -@FILTER_REGISTRY.register('delayed_on_off', DelayedOnOffFilter, - cv.positive_time_period_milliseconds) +@FILTER_REGISTRY.register( + "delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds +) def delayed_on_off_filter_to_code(config, filter_id): var = cg.new_Pvariable(filter_id, config) yield cg.register_component(var, {}) yield var -@FILTER_REGISTRY.register('delayed_on', DelayedOnFilter, - cv.positive_time_period_milliseconds) +@FILTER_REGISTRY.register( + "delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds +) def delayed_on_filter_to_code(config, filter_id): var = cg.new_Pvariable(filter_id, config) yield cg.register_component(var, {}) yield var -@FILTER_REGISTRY.register('delayed_off', DelayedOffFilter, cv.positive_time_period_milliseconds) +@FILTER_REGISTRY.register( + "delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds +) def delayed_off_filter_to_code(config, filter_id): var = cg.new_Pvariable(filter_id, config) yield cg.register_component(var, {}) yield var -@FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_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)) + lambda_ = yield cg.process_lambda( + config, [(bool, "x")], return_type=cg.optional.template(bool) + ) yield cg.new_Pvariable(filter_id, lambda_) -MULTI_CLICK_TIMING_SCHEMA = cv.Schema({ - cv.Optional(CONF_STATE): cv.boolean, - cv.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds, - cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds, -}) +MULTI_CLICK_TIMING_SCHEMA = cv.Schema( + { + cv.Optional(CONF_STATE): cv.boolean, + cv.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds, + cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds, + } +) def parse_multi_click_timing_str(value): if not isinstance(value, str): return value - parts = value.lower().split(' ') + parts = value.lower().split(" ") if len(parts) != 5: - raise cv.Invalid("Multi click timing grammar consists of exactly 5 words, not {}" - "".format(len(parts))) + raise cv.Invalid( + "Multi click timing grammar consists of exactly 5 words, not {}" + "".format(len(parts)) + ) try: state = cv.boolean(parts[0]) except cv.Invalid: # pylint: disable=raise-missing-from raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0])) - if parts[1] != 'for': + if parts[1] != "for": raise cv.Invalid("Second word must be 'for', got {}".format(parts[1])) - if parts[2] == 'at': - if parts[3] == 'least': + if parts[2] == "at": + if parts[3] == "least": key = CONF_MIN_LENGTH - elif parts[3] == 'most': + elif parts[3] == "most": key = CONF_MAX_LENGTH else: - raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}" - "".format(parts[3])) + raise cv.Invalid( + "Third word after at must either be 'least' or 'most', got {}" + "".format(parts[3]) + ) try: length = cv.positive_time_period_milliseconds(parts[4]) except cv.Invalid as err: raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}") - return { - CONF_STATE: state, - key: str(length) - } + return {CONF_STATE: state, key: str(length)} - if parts[3] != 'to': + if parts[3] != "to": raise cv.Invalid("Multi click grammar: 4th word must be 'to'") try: @@ -143,7 +226,7 @@ def parse_multi_click_timing_str(value): return { CONF_STATE: state, CONF_MIN_LENGTH: str(min_length), - CONF_MAX_LENGTH: str(max_length) + CONF_MAX_LENGTH: str(max_length), } @@ -163,11 +246,15 @@ def validate_multi_click_timing(value): new_state = v_.get(CONF_STATE, not state) if new_state == state: - raise cv.Invalid("Timings must have alternating state. Indices {} and {} have " - "the same state {}".format(i, i + 1, state)) + raise cv.Invalid( + "Timings must have alternating state. Indices {} and {} have " + "the same state {}".format(i, i + 1, state) + ) if max_length is not None and max_length < min_length: - raise cv.Invalid("Max length ({}) must be larger than min length ({})." - "".format(max_length, min_length)) + raise cv.Invalid( + "Max length ({}) must be larger than min length ({})." + "".format(max_length, min_length) + ) state = new_state tim = { @@ -180,46 +267,71 @@ def validate_multi_click_timing(value): return timings -device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space='_') - -BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(BinarySensor), - cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTBinarySensorComponent), - - cv.Optional(CONF_DEVICE_CLASS): device_class, - cv.Optional(CONF_FILTERS): validate_filters, - cv.Optional(CONF_ON_PRESS): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger), - }), - cv.Optional(CONF_ON_RELEASE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), - }), - cv.Optional(CONF_ON_CLICK): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), - cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, - }), - cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), - cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, - }), - cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), - cv.Required(CONF_TIMING): cv.All([parse_multi_click_timing_str], - validate_multi_click_timing), - cv.Optional(CONF_INVALID_COOLDOWN, default='1s'): cv.positive_time_period_milliseconds, - }), - cv.Optional(CONF_ON_STATE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), - }), - - cv.Optional(CONF_INVERTED): cv.invalid( - "The inverted binary_sensor property has been replaced by the " - "new 'invert' binary sensor filter. Please see " - "https://esphome.io/components/binary_sensor/index.html." - ), -}) +device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + +BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BinarySensor), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( + mqtt.MQTTBinarySensorComponent + ), + cv.Optional(CONF_DEVICE_CLASS): device_class, + cv.Optional(CONF_FILTERS): validate_filters, + cv.Optional(CONF_ON_PRESS): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger), + } + ), + cv.Optional(CONF_ON_RELEASE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), + } + ), + cv.Optional(CONF_ON_CLICK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), + cv.Optional( + CONF_MIN_LENGTH, default="50ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_MAX_LENGTH, default="350ms" + ): cv.positive_time_period_milliseconds, + } + ), + cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), + cv.Optional( + CONF_MIN_LENGTH, default="50ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_MAX_LENGTH, default="350ms" + ): cv.positive_time_period_milliseconds, + } + ), + cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), + cv.Required(CONF_TIMING): cv.All( + [parse_multi_click_timing_str], validate_multi_click_timing + ), + cv.Optional( + CONF_INVALID_COOLDOWN, default="1s" + ): cv.positive_time_period_milliseconds, + } + ), + cv.Optional(CONF_ON_STATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), + } + ), + cv.Optional(CONF_INVERTED): cv.invalid( + "The inverted binary_sensor property has been replaced by the " + "new 'invert' binary sensor filter. Please see " + "https://esphome.io/components/binary_sensor/index.html." + ), + } +) @coroutine @@ -244,24 +356,28 @@ def setup_binary_sensor_core_(var, config): yield automation.build_automation(trigger, [], conf) for conf in config.get(CONF_ON_CLICK, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, - conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]) + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH] + ) yield automation.build_automation(trigger, [], conf) for conf in config.get(CONF_ON_DOUBLE_CLICK, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, - conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]) + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH] + ) yield automation.build_automation(trigger, [], conf) for conf in config.get(CONF_ON_MULTI_CLICK, []): timings = [] for tim in conf[CONF_TIMING]: - timings.append(cg.StructInitializer( - MultiClickTriggerEvent, - ('state', tim[CONF_STATE]), - ('min_length', tim[CONF_MIN_LENGTH]), - ('max_length', tim.get(CONF_MAX_LENGTH, 4294967294)), - )) + timings.append( + cg.StructInitializer( + MultiClickTriggerEvent, + ("state", tim[CONF_STATE]), + ("min_length", tim[CONF_MIN_LENGTH]), + ("max_length", tim.get(CONF_MAX_LENGTH, 4294967294)), + ) + ) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings) if CONF_INVALID_COOLDOWN in conf: cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN])) @@ -270,7 +386,7 @@ def setup_binary_sensor_core_(var, config): for conf in config.get(CONF_ON_STATE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - yield automation.build_automation(trigger, [(bool, 'x')], conf) + yield automation.build_automation(trigger, [(bool, "x")], conf) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) @@ -292,22 +408,28 @@ def new_binary_sensor(config): yield var -BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(BinarySensor), - cv.Optional(CONF_FOR): cv.invalid("This option has been removed in 1.13, please use the " - "'for' condition instead."), -}) +BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(BinarySensor), + cv.Optional(CONF_FOR): cv.invalid( + "This option has been removed in 1.13, please use the " + "'for' condition instead." + ), + } +) -@automation.register_condition('binary_sensor.is_on', BinarySensorCondition, - BINARY_SENSOR_CONDITION_SCHEMA) +@automation.register_condition( + "binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA +) def binary_sensor_is_on_to_code(config, condition_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(condition_id, template_arg, paren, True) -@automation.register_condition('binary_sensor.is_off', BinarySensorCondition, - BINARY_SENSOR_CONDITION_SCHEMA) +@automation.register_condition( + "binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA +) def binary_sensor_is_off_to_code(config, condition_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(condition_id, template_arg, paren, False) @@ -315,5 +437,5 @@ def binary_sensor_is_off_to_code(config, condition_id, template_arg, args): @coroutine_with_priority(100.0) def to_code(config): - cg.add_define('USE_BINARY_SENSOR') + cg.add_define("USE_BINARY_SENSOR") cg.add_global(binary_sensor_ns.using) diff --git a/esphome/components/binary_sensor_map/sensor.py b/esphome/components/binary_sensor_map/sensor.py index 27f4654ded6f..81bc85e5705a 100644 --- a/esphome/components/binary_sensor_map/sensor.py +++ b/esphome/components/binary_sensor_map/sensor.py @@ -2,14 +2,25 @@ import esphome.config_validation as cv from esphome.components import sensor, binary_sensor -from esphome.const import CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, UNIT_EMPTY, \ - ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP - -DEPENDENCIES = ['binary_sensor'] - -binary_sensor_map_ns = cg.esphome_ns.namespace('binary_sensor_map') -BinarySensorMap = binary_sensor_map_ns.class_('BinarySensorMap', cg.Component, sensor.Sensor) -SensorMapType = binary_sensor_map_ns.enum('SensorMapType') +from esphome.const import ( + CONF_ID, + CONF_CHANNELS, + CONF_VALUE, + CONF_TYPE, + DEVICE_CLASS_EMPTY, + UNIT_EMPTY, + ICON_CHECK_CIRCLE_OUTLINE, + CONF_BINARY_SENSOR, + CONF_GROUP, +) + +DEPENDENCIES = ["binary_sensor"] + +binary_sensor_map_ns = cg.esphome_ns.namespace("binary_sensor_map") +BinarySensorMap = binary_sensor_map_ns.class_( + "BinarySensorMap", cg.Component, sensor.Sensor +) +SensorMapType = binary_sensor_map_ns.enum("SensorMapType") SENSOR_MAP_TYPES = { CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, @@ -20,12 +31,21 @@ cv.Required(CONF_VALUE): cv.float_, } -CONFIG_SCHEMA = cv.typed_schema({ - CONF_GROUP: sensor.sensor_schema(UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0).extend({ - cv.GenerateID(): cv.declare_id(BinarySensorMap), - cv.Required(CONF_CHANNELS): cv.All(cv.ensure_list(entry), cv.Length(min=1)), - }), -}, lower=True) +CONFIG_SCHEMA = cv.typed_schema( + { + CONF_GROUP: sensor.sensor_schema( + UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0, DEVICE_CLASS_EMPTY + ).extend( + { + cv.GenerateID(): cv.declare_id(BinarySensorMap), + cv.Required(CONF_CHANNELS): cv.All( + cv.ensure_list(entry), cv.Length(min=1) + ), + } + ), + }, + lower=True, +) def to_code(config): diff --git a/esphome/components/ble_presence/binary_sensor.py b/esphome/components/ble_presence/binary_sensor.py index 1cf80093846e..4c6e7ee5673c 100644 --- a/esphome/components/ble_presence/binary_sensor.py +++ b/esphome/components/ble_presence/binary_sensor.py @@ -3,18 +3,28 @@ from esphome.components import binary_sensor, esp32_ble_tracker from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID -DEPENDENCIES = ['esp32_ble_tracker'] +DEPENDENCIES = ["esp32_ble_tracker"] -ble_presence_ns = cg.esphome_ns.namespace('ble_presence') -BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor, - cg.Component, esp32_ble_tracker.ESPBTDeviceListener) +ble_presence_ns = cg.esphome_ns.namespace("ble_presence") +BLEPresenceDevice = ble_presence_ns.class_( + "BLEPresenceDevice", + binary_sensor.BinarySensor, + cg.Component, + esp32_ble_tracker.ESPBTDeviceListener, +) -CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(BLEPresenceDevice), - cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( - cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID)) +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BLEPresenceDevice), + cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA), + cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID), +) def to_code(config): @@ -28,9 +38,17 @@ def to_code(config): if CONF_SERVICE_UUID in config: if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): - cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) + cg.add( + var.set_service_uuid16( + esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) + ) + ) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): - cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) + cg.add( + var.set_service_uuid32( + esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) + ) + ) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid128(uuid128)) diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index 76a27e6f2bc6..f6ee209ae10c 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -1,20 +1,35 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_SERVICE_UUID, CONF_MAC_ADDRESS, CONF_ID, UNIT_DECIBEL, ICON_SIGNAL +from esphome.const import ( + CONF_SERVICE_UUID, + CONF_MAC_ADDRESS, + CONF_ID, + DEVICE_CLASS_SIGNAL_STRENGTH, + UNIT_DECIBEL, + ICON_EMPTY, +) -DEPENDENCIES = ['esp32_ble_tracker'] +DEPENDENCIES = ["esp32_ble_tracker"] -ble_rssi_ns = cg.esphome_ns.namespace('ble_rssi') -BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component, - esp32_ble_tracker.ESPBTDeviceListener) +ble_rssi_ns = cg.esphome_ns.namespace("ble_rssi") +BLERSSISensor = ble_rssi_ns.class_( + "BLERSSISensor", sensor.Sensor, cg.Component, esp32_ble_tracker.ESPBTDeviceListener +) -CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({ - cv.GenerateID(): cv.declare_id(BLERSSISensor), - cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( - cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID)) +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema(UNIT_DECIBEL, ICON_EMPTY, 0, DEVICE_CLASS_SIGNAL_STRENGTH) + .extend( + { + cv.GenerateID(): cv.declare_id(BLERSSISensor), + cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA), + cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID), +) def to_code(config): @@ -28,9 +43,17 @@ def to_code(config): if CONF_SERVICE_UUID in config: if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): - cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) + cg.add( + var.set_service_uuid16( + esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) + ) + ) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): - cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) + cg.add( + var.set_service_uuid32( + esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) + ) + ) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid128(uuid128)) diff --git a/esphome/components/ble_scanner/text_sensor.py b/esphome/components/ble_scanner/text_sensor.py index 1a43ffb68aff..96c7b4d88770 100644 --- a/esphome/components/ble_scanner/text_sensor.py +++ b/esphome/components/ble_scanner/text_sensor.py @@ -3,16 +3,25 @@ from esphome.components import text_sensor, esp32_ble_tracker from esphome.const import CONF_ID -DEPENDENCIES = ['esp32_ble_tracker'] +DEPENDENCIES = ["esp32_ble_tracker"] -ble_scanner_ns = cg.esphome_ns.namespace('ble_scanner') -BLEScanner = ble_scanner_ns.class_('BLEScanner', text_sensor.TextSensor, cg.Component, - esp32_ble_tracker.ESPBTDeviceListener) +ble_scanner_ns = cg.esphome_ns.namespace("ble_scanner") +BLEScanner = ble_scanner_ns.class_( + "BLEScanner", + text_sensor.TextSensor, + cg.Component, + esp32_ble_tracker.ESPBTDeviceListener, +) -CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(BLEScanner), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( - cv.COMPONENT_SCHEMA)) +CONFIG_SCHEMA = cv.All( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BLEScanner), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/bme280/sensor.py b/esphome/components/bme280/sensor.py index 651752102f19..5b0e418a66f5 100644 --- a/esphome/components/bme280/sensor.py +++ b/esphome/components/bme280/sensor.py @@ -1,53 +1,87 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, \ - CONF_PRESSURE, CONF_TEMPERATURE, ICON_THERMOMETER, \ - UNIT_CELSIUS, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_IIR_FILTER, + CONF_OVERSAMPLING, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + UNIT_PERCENT, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -bme280_ns = cg.esphome_ns.namespace('bme280') -BME280Oversampling = bme280_ns.enum('BME280Oversampling') +bme280_ns = cg.esphome_ns.namespace("bme280") +BME280Oversampling = bme280_ns.enum("BME280Oversampling") OVERSAMPLING_OPTIONS = { - 'NONE': BME280Oversampling.BME280_OVERSAMPLING_NONE, - '1X': BME280Oversampling.BME280_OVERSAMPLING_1X, - '2X': BME280Oversampling.BME280_OVERSAMPLING_2X, - '4X': BME280Oversampling.BME280_OVERSAMPLING_4X, - '8X': BME280Oversampling.BME280_OVERSAMPLING_8X, - '16X': BME280Oversampling.BME280_OVERSAMPLING_16X, + "NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE, + "1X": BME280Oversampling.BME280_OVERSAMPLING_1X, + "2X": BME280Oversampling.BME280_OVERSAMPLING_2X, + "4X": BME280Oversampling.BME280_OVERSAMPLING_4X, + "8X": BME280Oversampling.BME280_OVERSAMPLING_8X, + "16X": BME280Oversampling.BME280_OVERSAMPLING_16X, } -BME280IIRFilter = bme280_ns.enum('BME280IIRFilter') +BME280IIRFilter = bme280_ns.enum("BME280IIRFilter") IIR_FILTER_OPTIONS = { - 'OFF': BME280IIRFilter.BME280_IIR_FILTER_OFF, - '2X': BME280IIRFilter.BME280_IIR_FILTER_2X, - '4X': BME280IIRFilter.BME280_IIR_FILTER_4X, - '8X': BME280IIRFilter.BME280_IIR_FILTER_8X, - '16X': BME280IIRFilter.BME280_IIR_FILTER_16X, + "OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF, + "2X": BME280IIRFilter.BME280_IIR_FILTER_2X, + "4X": BME280IIRFilter.BME280_IIR_FILTER_4X, + "8X": BME280IIRFilter.BME280_IIR_FILTER_8X, + "16X": BME280IIRFilter.BME280_IIR_FILTER_16X, } -BME280Component = bme280_ns.class_('BME280Component', cg.PollingComponent, i2c.I2CDevice) +BME280Component = bme280_ns.class_( + "BME280Component", cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(BME280Component), - cv.Optional(CONF_TEMPERATURE): - sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.Optional(CONF_OVERSAMPLING, default='16X'): - cv.enum(OVERSAMPLING_OPTIONS, upper=True), - }), - cv.Optional(CONF_PRESSURE): - sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({ - cv.Optional(CONF_OVERSAMPLING, default='16X'): - cv.enum(OVERSAMPLING_OPTIONS, upper=True), - }), - cv.Optional(CONF_HUMIDITY): - sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({ - cv.Optional(CONF_OVERSAMPLING, default='16X'): - cv.enum(OVERSAMPLING_OPTIONS, upper=True), - }), - cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BME280Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( + IIR_FILTER_OPTIONS, upper=True + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x77)) +) def to_code(config): diff --git a/esphome/components/bme680/sensor.py b/esphome/components/bme680/sensor.py index 64973fb91ced..a82ecefe9909 100644 --- a/esphome/components/bme680/sensor.py +++ b/esphome/components/bme680/sensor.py @@ -2,64 +2,116 @@ import esphome.config_validation as cv from esphome import core from esphome.components import i2c, sensor -from esphome.const import CONF_DURATION, CONF_GAS_RESISTANCE, CONF_HEATER, \ - CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, CONF_PRESSURE, \ - CONF_TEMPERATURE, UNIT_OHM, ICON_GAS_CYLINDER, UNIT_CELSIUS, \ - ICON_THERMOMETER, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT +from esphome.const import ( + CONF_DURATION, + CONF_GAS_RESISTANCE, + CONF_HEATER, + CONF_HUMIDITY, + CONF_ID, + CONF_IIR_FILTER, + CONF_OVERSAMPLING, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + UNIT_OHM, + ICON_GAS_CYLINDER, + UNIT_CELSIUS, + ICON_EMPTY, + UNIT_HECTOPASCAL, + UNIT_PERCENT, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -bme680_ns = cg.esphome_ns.namespace('bme680') -BME680Oversampling = bme680_ns.enum('BME680Oversampling') +bme680_ns = cg.esphome_ns.namespace("bme680") +BME680Oversampling = bme680_ns.enum("BME680Oversampling") OVERSAMPLING_OPTIONS = { - 'NONE': BME680Oversampling.BME680_OVERSAMPLING_NONE, - '1X': BME680Oversampling.BME680_OVERSAMPLING_1X, - '2X': BME680Oversampling.BME680_OVERSAMPLING_2X, - '4X': BME680Oversampling.BME680_OVERSAMPLING_4X, - '8X': BME680Oversampling.BME680_OVERSAMPLING_8X, - '16X': BME680Oversampling.BME680_OVERSAMPLING_16X, + "NONE": BME680Oversampling.BME680_OVERSAMPLING_NONE, + "1X": BME680Oversampling.BME680_OVERSAMPLING_1X, + "2X": BME680Oversampling.BME680_OVERSAMPLING_2X, + "4X": BME680Oversampling.BME680_OVERSAMPLING_4X, + "8X": BME680Oversampling.BME680_OVERSAMPLING_8X, + "16X": BME680Oversampling.BME680_OVERSAMPLING_16X, } -BME680IIRFilter = bme680_ns.enum('BME680IIRFilter') +BME680IIRFilter = bme680_ns.enum("BME680IIRFilter") IIR_FILTER_OPTIONS = { - 'OFF': BME680IIRFilter.BME680_IIR_FILTER_OFF, - '1X': BME680IIRFilter.BME680_IIR_FILTER_1X, - '3X': BME680IIRFilter.BME680_IIR_FILTER_3X, - '7X': BME680IIRFilter.BME680_IIR_FILTER_7X, - '15X': BME680IIRFilter.BME680_IIR_FILTER_15X, - '31X': BME680IIRFilter.BME680_IIR_FILTER_31X, - '63X': BME680IIRFilter.BME680_IIR_FILTER_63X, - '127X': BME680IIRFilter.BME680_IIR_FILTER_127X, + "OFF": BME680IIRFilter.BME680_IIR_FILTER_OFF, + "1X": BME680IIRFilter.BME680_IIR_FILTER_1X, + "3X": BME680IIRFilter.BME680_IIR_FILTER_3X, + "7X": BME680IIRFilter.BME680_IIR_FILTER_7X, + "15X": BME680IIRFilter.BME680_IIR_FILTER_15X, + "31X": BME680IIRFilter.BME680_IIR_FILTER_31X, + "63X": BME680IIRFilter.BME680_IIR_FILTER_63X, + "127X": BME680IIRFilter.BME680_IIR_FILTER_127X, } -BME680Component = bme680_ns.class_('BME680Component', cg.PollingComponent, i2c.I2CDevice) +BME680Component = bme680_ns.class_( + "BME680Component", cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(BME680Component), - cv.Optional(CONF_TEMPERATURE): - sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.Optional(CONF_OVERSAMPLING, default='16X'): - cv.enum(OVERSAMPLING_OPTIONS, upper=True), - }), - cv.Optional(CONF_PRESSURE): - sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({ - cv.Optional(CONF_OVERSAMPLING, default='16X'): - cv.enum(OVERSAMPLING_OPTIONS, upper=True), - }), - cv.Optional(CONF_HUMIDITY): - sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({ - cv.Optional(CONF_OVERSAMPLING, default='16X'): - cv.enum(OVERSAMPLING_OPTIONS, upper=True), - }), - cv.Optional(CONF_GAS_RESISTANCE): - sensor.sensor_schema(UNIT_OHM, ICON_GAS_CYLINDER, 1), - cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True), - cv.Optional(CONF_HEATER): cv.Any(None, cv.All(cv.Schema({ - cv.Optional(CONF_TEMPERATURE, default=320): cv.int_range(min=200, max=400), - cv.Optional(CONF_DURATION, default='150ms'): cv.All( - cv.positive_time_period_milliseconds, cv.Range(max=core.TimePeriod(milliseconds=4032))) - }), cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION))), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x76)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BME680Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema( + UNIT_OHM, ICON_GAS_CYLINDER, 1, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( + IIR_FILTER_OPTIONS, upper=True + ), + cv.Optional(CONF_HEATER): cv.Any( + None, + cv.All( + cv.Schema( + { + cv.Optional(CONF_TEMPERATURE, default=320): cv.int_range( + min=200, max=400 + ), + cv.Optional(CONF_DURATION, default="150ms"): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=core.TimePeriod(milliseconds=4032)), + ), + } + ), + cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION), + ), + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x76)) +) def to_code(config): diff --git a/esphome/components/bmp085/sensor.py b/esphome/components/bmp085/sensor.py index 558c6978b1db..a070e4aa69d3 100644 --- a/esphome/components/bmp085/sensor.py +++ b/esphome/components/bmp085/sensor.py @@ -1,19 +1,39 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL +from esphome.const import ( + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + UNIT_CELSIUS, + ICON_EMPTY, + UNIT_HECTOPASCAL, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -bmp085_ns = cg.esphome_ns.namespace('bmp085') -BMP085Component = bmp085_ns.class_('BMP085Component', cg.PollingComponent, i2c.I2CDevice) +bmp085_ns = cg.esphome_ns.namespace("bmp085") +BMP085Component = bmp085_ns.class_( + "BMP085Component", cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(BMP085Component), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BMP085Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x77)) +) def to_code(config): diff --git a/esphome/components/bmp280/sensor.py b/esphome/components/bmp280/sensor.py index 63c9655331df..b12d7bff7fe9 100644 --- a/esphome/components/bmp280/sensor.py +++ b/esphome/components/bmp280/sensor.py @@ -1,44 +1,75 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL, \ - CONF_IIR_FILTER, CONF_OVERSAMPLING +from esphome.const import ( + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + UNIT_CELSIUS, + ICON_EMPTY, + UNIT_HECTOPASCAL, + CONF_IIR_FILTER, + CONF_OVERSAMPLING, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -bmp280_ns = cg.esphome_ns.namespace('bmp280') -BMP280Oversampling = bmp280_ns.enum('BMP280Oversampling') +bmp280_ns = cg.esphome_ns.namespace("bmp280") +BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling") OVERSAMPLING_OPTIONS = { - 'NONE': BMP280Oversampling.BMP280_OVERSAMPLING_NONE, - '1X': BMP280Oversampling.BMP280_OVERSAMPLING_1X, - '2X': BMP280Oversampling.BMP280_OVERSAMPLING_2X, - '4X': BMP280Oversampling.BMP280_OVERSAMPLING_4X, - '8X': BMP280Oversampling.BMP280_OVERSAMPLING_8X, - '16X': BMP280Oversampling.BMP280_OVERSAMPLING_16X, + "NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE, + "1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X, + "2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X, + "4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X, + "8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X, + "16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X, } -BMP280IIRFilter = bmp280_ns.enum('BMP280IIRFilter') +BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter") IIR_FILTER_OPTIONS = { - 'OFF': BMP280IIRFilter.BMP280_IIR_FILTER_OFF, - '2X': BMP280IIRFilter.BMP280_IIR_FILTER_2X, - '4X': BMP280IIRFilter.BMP280_IIR_FILTER_4X, - '8X': BMP280IIRFilter.BMP280_IIR_FILTER_8X, - '16X': BMP280IIRFilter.BMP280_IIR_FILTER_16X, + "OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF, + "2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X, + "4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X, + "8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X, + "16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X, } -BMP280Component = bmp280_ns.class_('BMP280Component', cg.PollingComponent, i2c.I2CDevice) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(BMP280Component), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.Optional(CONF_OVERSAMPLING, default='16X'): cv.enum(OVERSAMPLING_OPTIONS, upper=True), - }), - cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({ - cv.Optional(CONF_OVERSAMPLING, default='16X'): cv.enum(OVERSAMPLING_OPTIONS, upper=True), - }), - cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77)) +BMP280Component = bmp280_ns.class_( + "BMP280Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BMP280Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE + ).extend( + { + cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( + OVERSAMPLING_OPTIONS, upper=True + ), + } + ), + cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( + IIR_FILTER_OPTIONS, upper=True + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x77)) +) def to_code(config): diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index 24decc14b019..1cbebb07da4a 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -4,68 +4,82 @@ from esphome.core import CORE, coroutine from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_DATA -CODEOWNERS = ['@mvturnho', '@danielschramm'] +CODEOWNERS = ["@mvturnho", "@danielschramm"] IS_PLATFORM_COMPONENT = True -CONF_CAN_ID = 'can_id' -CONF_USE_EXTENDED_ID = 'use_extended_id' -CONF_CANBUS_ID = 'canbus_id' -CONF_BIT_RATE = 'bit_rate' -CONF_ON_FRAME = 'on_frame' -CONF_CANBUS_SEND = 'canbus.send' +CONF_CAN_ID = "can_id" +CONF_USE_EXTENDED_ID = "use_extended_id" +CONF_CANBUS_ID = "canbus_id" +CONF_BIT_RATE = "bit_rate" +CONF_ON_FRAME = "on_frame" def validate_id(id_value, id_ext): if not id_ext: - if id_value > 0x7ff: + if id_value > 0x7FF: raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") def validate_raw_data(value): if isinstance(value, str): - return value.encode('utf-8') + return value.encode("utf-8") if isinstance(value, list): return cv.Schema([cv.hex_uint8_t])(value) - raise cv.Invalid("data must either be a string wrapped in quotes or a list of bytes") + raise cv.Invalid( + "data must either be a string wrapped in quotes or a list of bytes" + ) -canbus_ns = cg.esphome_ns.namespace('canbus') -CanbusComponent = canbus_ns.class_('CanbusComponent', cg.Component) -CanbusTrigger = canbus_ns.class_('CanbusTrigger', - automation.Trigger.template(cg.std_vector.template(cg.uint8)), - cg.Component) -CanSpeed = canbus_ns.enum('CAN_SPEED') +canbus_ns = cg.esphome_ns.namespace("canbus") +CanbusComponent = canbus_ns.class_("CanbusComponent", cg.Component) +CanbusTrigger = canbus_ns.class_( + "CanbusTrigger", + automation.Trigger.template(cg.std_vector.template(cg.uint8)), + cg.Component, +) +CanSpeed = canbus_ns.enum("CAN_SPEED") CAN_SPEEDS = { - '5KBPS': CanSpeed.CAN_5KBPS, - '10KBPS': CanSpeed.CAN_10KBPS, - '20KBPS': CanSpeed.CAN_20KBPS, - '31K25BPS': CanSpeed.CAN_31K25BPS, - '33KBPS': CanSpeed.CAN_33KBPS, - '40KBPS': CanSpeed.CAN_40KBPS, - '50KBPS': CanSpeed.CAN_50KBPS, - '80KBPS': CanSpeed.CAN_80KBPS, - '83K3BPS': CanSpeed.CAN_83K3BPS, - '95KBPS': CanSpeed.CAN_95KBPS, - '100KBPS': CanSpeed.CAN_100KBPS, - '125KBPS': CanSpeed.CAN_125KBPS, - '200KBPS': CanSpeed.CAN_200KBPS, - '250KBPS': CanSpeed.CAN_250KBPS, - '500KBPS': CanSpeed.CAN_500KBPS, - '1000KBPS': CanSpeed.CAN_1000KBPS, + "5KBPS": CanSpeed.CAN_5KBPS, + "10KBPS": CanSpeed.CAN_10KBPS, + "20KBPS": CanSpeed.CAN_20KBPS, + "31K25BPS": CanSpeed.CAN_31K25BPS, + "33KBPS": CanSpeed.CAN_33KBPS, + "40KBPS": CanSpeed.CAN_40KBPS, + "50KBPS": CanSpeed.CAN_50KBPS, + "80KBPS": CanSpeed.CAN_80KBPS, + "83K3BPS": CanSpeed.CAN_83K3BPS, + "95KBPS": CanSpeed.CAN_95KBPS, + "100KBPS": CanSpeed.CAN_100KBPS, + "125KBPS": CanSpeed.CAN_125KBPS, + "200KBPS": CanSpeed.CAN_200KBPS, + "250KBPS": CanSpeed.CAN_250KBPS, + "500KBPS": CanSpeed.CAN_500KBPS, + "1000KBPS": CanSpeed.CAN_1000KBPS, } -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(CanbusComponent), - cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff), - cv.Optional(CONF_BIT_RATE, default='125KBPS'): cv.enum(CAN_SPEEDS, upper=True), - cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, - cv.Optional(CONF_ON_FRAME): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), - cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff), +CANBUS_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CanbusComponent), + cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), + cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True), cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, - }), -}).extend(cv.COMPONENT_SCHEMA) + cv.Optional(CONF_ON_FRAME): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), + cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), + cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, + cv.Optional(CONF_ON_FRAME): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), + cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), + cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, + } + ), + } + ), + } +).extend(cv.COMPONENT_SCHEMA) @coroutine @@ -82,7 +96,9 @@ def setup_canbus_core_(var, config): validate_id(can_id, ext_id) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id) yield cg.register_component(trigger, conf) - yield automation.build_automation(trigger, [(cg.std_vector.template(cg.uint8), 'x')], conf) + yield automation.build_automation( + trigger, [(cg.std_vector.template(cg.uint8), "x")], conf + ) @coroutine @@ -93,14 +109,19 @@ def register_canbus(var, config): # Actions -@automation.register_action(CONF_CANBUS_SEND, - canbus_ns.class_('CanbusSendAction', automation.Action), - cv.maybe_simple_value({ - cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent), - cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff), - cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), - }, key=CONF_DATA)) +@automation.register_action( + "canbus.send", + canbus_ns.class_("CanbusSendAction", automation.Action), + cv.maybe_simple_value( + { + cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent), + cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), + cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, + cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + }, + key=CONF_DATA, + ), +) def canbus_action_to_code(config, action_id, template_arg, args): validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID]) var = cg.new_Pvariable(action_id, template_arg) @@ -110,7 +131,9 @@ def canbus_action_to_code(config, action_id, template_arg, args): can_id = yield cg.templatable(config[CONF_CAN_ID], args, cg.uint32) cg.add(var.set_can_id(can_id)) - use_extended_id = yield cg.templatable(config[CONF_USE_EXTENDED_ID], args, cg.uint32) + use_extended_id = yield cg.templatable( + config[CONF_USE_EXTENDED_ID], args, cg.uint32 + ) cg.add(var.set_use_extended_id(use_extended_id)) data = config[CONF_DATA] diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index bb8b2198a11a..e158db674630 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -5,17 +5,21 @@ from esphome.const import CONF_ID from esphome.core import coroutine_with_priority -AUTO_LOAD = ['web_server_base'] -DEPENDENCIES = ['wifi'] -CODEOWNERS = ['@OttoWinter'] +AUTO_LOAD = ["web_server_base"] +DEPENDENCIES = ["wifi"] +CODEOWNERS = ["@OttoWinter"] -captive_portal_ns = cg.esphome_ns.namespace('captive_portal') -CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component) +captive_portal_ns = cg.esphome_ns.namespace("captive_portal") +CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(CaptivePortal), - cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CaptivePortal), + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( + web_server_base.WebServerBase + ), + } +).extend(cv.COMPONENT_SCHEMA) @coroutine_with_priority(64.0) @@ -24,4 +28,4 @@ def to_code(config): var = cg.new_Pvariable(config[CONF_ID], paren) yield cg.register_component(var, config) - cg.add_define('USE_CAPTIVE_PORTAL') + cg.add_define("USE_CAPTIVE_PORTAL") diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index 0f755d5c11a6..95b108a22506 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -1,28 +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_MOLECULE_CO2 +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_EMPTY, + ICON_RADIATOR, + UNIT_PARTS_PER_MILLION, + UNIT_PARTS_PER_BILLION, + CONF_TEMPERATURE, + CONF_TVOC, + CONF_HUMIDITY, + ICON_MOLECULE_CO2, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -ccs811_ns = cg.esphome_ns.namespace('ccs811') -CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice) +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' +CONF_ECO2 = "eco2" +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_MOLECULE_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)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CCS811Component), + cv.Required(CONF_ECO2): sensor.sensor_schema( + UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY + ), + cv.Required(CONF_TVOC): sensor.sensor_schema( + UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY + ), + 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): diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 38e254bb9d01..7f74b62c61e5 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -2,70 +2,87 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt -from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, \ - CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \ - CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \ - CONF_MQTT_ID, CONF_NAME, CONF_FAN_MODE, CONF_SWING_MODE +from esphome.const import ( + CONF_AWAY, + CONF_ID, + CONF_INTERNAL, + CONF_MAX_TEMPERATURE, + CONF_MIN_TEMPERATURE, + CONF_MODE, + CONF_TARGET_TEMPERATURE, + CONF_TARGET_TEMPERATURE_HIGH, + CONF_TARGET_TEMPERATURE_LOW, + CONF_TEMPERATURE_STEP, + CONF_VISUAL, + CONF_MQTT_ID, + CONF_NAME, + CONF_FAN_MODE, + CONF_SWING_MODE, +) from esphome.core import CORE, coroutine, coroutine_with_priority IS_PLATFORM_COMPONENT = True -CODEOWNERS = ['@esphome/core'] -climate_ns = cg.esphome_ns.namespace('climate') +CODEOWNERS = ["@esphome/core"] +climate_ns = cg.esphome_ns.namespace("climate") -Climate = climate_ns.class_('Climate', cg.Nameable) -ClimateCall = climate_ns.class_('ClimateCall') -ClimateTraits = climate_ns.class_('ClimateTraits') +Climate = climate_ns.class_("Climate", cg.Nameable) +ClimateCall = climate_ns.class_("ClimateCall") +ClimateTraits = climate_ns.class_("ClimateTraits") -ClimateMode = climate_ns.enum('ClimateMode') +ClimateMode = climate_ns.enum("ClimateMode") CLIMATE_MODES = { - 'OFF': ClimateMode.CLIMATE_MODE_OFF, - 'AUTO': ClimateMode.CLIMATE_MODE_AUTO, - 'COOL': ClimateMode.CLIMATE_MODE_COOL, - 'HEAT': ClimateMode.CLIMATE_MODE_HEAT, - 'DRY': ClimateMode.CLIMATE_MODE_DRY, - 'FAN_ONLY': ClimateMode.CLIMATE_MODE_FAN_ONLY, + "OFF": ClimateMode.CLIMATE_MODE_OFF, + "AUTO": ClimateMode.CLIMATE_MODE_AUTO, + "COOL": ClimateMode.CLIMATE_MODE_COOL, + "HEAT": ClimateMode.CLIMATE_MODE_HEAT, + "DRY": ClimateMode.CLIMATE_MODE_DRY, + "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, } validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) -ClimateFanMode = climate_ns.enum('ClimateFanMode') +ClimateFanMode = climate_ns.enum("ClimateFanMode") CLIMATE_FAN_MODES = { - 'ON': ClimateFanMode.CLIMATE_FAN_ON, - 'OFF': ClimateFanMode.CLIMATE_FAN_OFF, - 'AUTO': ClimateFanMode.CLIMATE_FAN_AUTO, - 'LOW': ClimateFanMode.CLIMATE_FAN_LOW, - 'MEDIUM': ClimateFanMode.CLIMATE_FAN_MEDIUM, - 'HIGH': ClimateFanMode.CLIMATE_FAN_HIGH, - 'MIDDLE': ClimateFanMode.CLIMATE_FAN_MIDDLE, - 'FOCUS': ClimateFanMode.CLIMATE_FAN_FOCUS, - 'DIFFUSE': ClimateFanMode.CLIMATE_FAN_DIFFUSE, + "ON": ClimateFanMode.CLIMATE_FAN_ON, + "OFF": ClimateFanMode.CLIMATE_FAN_OFF, + "AUTO": ClimateFanMode.CLIMATE_FAN_AUTO, + "LOW": ClimateFanMode.CLIMATE_FAN_LOW, + "MEDIUM": ClimateFanMode.CLIMATE_FAN_MEDIUM, + "HIGH": ClimateFanMode.CLIMATE_FAN_HIGH, + "MIDDLE": ClimateFanMode.CLIMATE_FAN_MIDDLE, + "FOCUS": ClimateFanMode.CLIMATE_FAN_FOCUS, + "DIFFUSE": ClimateFanMode.CLIMATE_FAN_DIFFUSE, } validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True) -ClimateSwingMode = climate_ns.enum('ClimateSwingMode') +ClimateSwingMode = climate_ns.enum("ClimateSwingMode") CLIMATE_SWING_MODES = { - 'OFF': ClimateSwingMode.CLIMATE_SWING_OFF, - 'BOTH': ClimateSwingMode.CLIMATE_SWING_BOTH, - 'VERTICAL': ClimateSwingMode.CLIMATE_SWING_VERTICAL, - 'HORIZONTAL': ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, + "OFF": ClimateSwingMode.CLIMATE_SWING_OFF, + "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, + "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, + "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, } validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) # Actions -ControlAction = climate_ns.class_('ControlAction', automation.Action) - -CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ - 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, - cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, - cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature, - }), - # TODO: MQTT topic options -}) +ControlAction = climate_ns.class_("ControlAction", automation.Action) + +CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( + { + 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, + cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, + cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature, + } + ), + # TODO: MQTT topic options + } +) @coroutine @@ -94,19 +111,23 @@ def register_climate(var, config): yield setup_climate_core_(var, config) -CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({ - 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), - cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), - cv.Optional(CONF_AWAY): cv.templatable(cv.boolean), - cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode), - cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode), -}) +CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema( + { + 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), + cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), + cv.Optional(CONF_AWAY): cv.templatable(cv.boolean), + cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode), + cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode), + } +) -@automation.register_action('climate.control', ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA) +@automation.register_action( + "climate.control", ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA +) def climate_control_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -117,10 +138,14 @@ def climate_control_to_code(config, action_id, template_arg, args): template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE], args, float) cg.add(var.set_target_temperature(template_)) if CONF_TARGET_TEMPERATURE_LOW in config: - template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE_LOW], args, float) + template_ = yield cg.templatable( + config[CONF_TARGET_TEMPERATURE_LOW], args, float + ) cg.add(var.set_target_temperature_low(template_)) if CONF_TARGET_TEMPERATURE_HIGH in config: - template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE_HIGH], args, float) + template_ = yield cg.templatable( + config[CONF_TARGET_TEMPERATURE_HIGH], args, float + ) cg.add(var.set_target_temperature_high(template_)) if CONF_AWAY in config: template_ = yield cg.templatable(config[CONF_AWAY], args, bool) @@ -129,12 +154,14 @@ def climate_control_to_code(config, action_id, template_arg, args): template_ = yield cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) cg.add(var.set_fan_mode(template_)) if CONF_SWING_MODE in config: - template_ = yield cg.templatable(config[CONF_SWING_MODE], args, ClimateSwingMode) + template_ = yield cg.templatable( + config[CONF_SWING_MODE], args, ClimateSwingMode + ) cg.add(var.set_swing_mode(template_)) yield var @coroutine_with_priority(100.0) def to_code(config): - cg.add_define('USE_CLIMATE') + cg.add_define("USE_CLIMATE") cg.add_global(climate_ns.using) diff --git a/esphome/components/climate_ir/__init__.py b/esphome/components/climate_ir/__init__.py index 40ab3f22e85d..8dcd75c31f82 100644 --- a/esphome/components/climate_ir/__init__.py +++ b/esphome/components/climate_ir/__init__.py @@ -1,27 +1,42 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import climate, remote_transmitter, remote_receiver, sensor, remote_base +from esphome.components import ( + climate, + remote_transmitter, + remote_receiver, + sensor, + remote_base, +) from esphome.components.remote_base import CONF_RECEIVER_ID, CONF_TRANSMITTER_ID from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR from esphome.core import coroutine -AUTO_LOAD = ['sensor', 'remote_base'] -CODEOWNERS = ['@glmnet'] - -climate_ir_ns = cg.esphome_ns.namespace('climate_ir') -ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component, - remote_base.RemoteReceiverListener) - -CLIMATE_IR_SCHEMA = climate.CLIMATE_SCHEMA.extend({ - cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent), - cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, - cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, - cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), -}).extend(cv.COMPONENT_SCHEMA) - -CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend({ - cv.Optional(CONF_RECEIVER_ID): cv.use_id(remote_receiver.RemoteReceiverComponent), -}) +AUTO_LOAD = ["sensor", "remote_base"] +CODEOWNERS = ["@glmnet"] + +climate_ir_ns = cg.esphome_ns.namespace("climate_ir") +ClimateIR = climate_ir_ns.class_( + "ClimateIR", climate.Climate, cg.Component, remote_base.RemoteReceiverListener +) + +CLIMATE_IR_SCHEMA = climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id( + remote_transmitter.RemoteTransmitterComponent + ), + cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, + cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, + cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), + } +).extend(cv.COMPONENT_SCHEMA) + +CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend( + { + cv.Optional(CONF_RECEIVER_ID): cv.use_id( + remote_receiver.RemoteReceiverComponent + ), + } +) @coroutine diff --git a/esphome/components/climate_ir_lg/climate.py b/esphome/components/climate_ir_lg/climate.py index 37bf9e2628e0..06e538d9c7a3 100644 --- a/esphome/components/climate_ir_lg/climate.py +++ b/esphome/components/climate_ir_lg/climate.py @@ -3,16 +3,45 @@ from esphome.components import climate_ir from esphome.const import CONF_ID -AUTO_LOAD = ['climate_ir'] +AUTO_LOAD = ["climate_ir"] -climate_ir_lg_ns = cg.esphome_ns.namespace('climate_ir_lg') -LgIrClimate = climate_ir_lg_ns.class_('LgIrClimate', climate_ir.ClimateIR) +climate_ir_lg_ns = cg.esphome_ns.namespace("climate_ir_lg") +LgIrClimate = climate_ir_lg_ns.class_("LgIrClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(LgIrClimate), -}) +CONF_HEADER_HIGH = "header_high" +CONF_HEADER_LOW = "header_low" +CONF_BIT_HIGH = "bit_high" +CONF_BIT_ONE_LOW = "bit_one_low" +CONF_BIT_ZERO_LOW = "bit_zero_low" + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(LgIrClimate), + cv.Optional( + CONF_HEADER_HIGH, default="8000us" + ): cv.positive_time_period_microseconds, + cv.Optional( + CONF_HEADER_LOW, default="4000us" + ): cv.positive_time_period_microseconds, + cv.Optional( + CONF_BIT_HIGH, default="600us" + ): cv.positive_time_period_microseconds, + cv.Optional( + CONF_BIT_ONE_LOW, default="1600us" + ): cv.positive_time_period_microseconds, + cv.Optional( + CONF_BIT_ZERO_LOW, default="550us" + ): cv.positive_time_period_microseconds, + } +) def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) yield climate_ir.register_climate_ir(var, config) + + cg.add(var.set_header_high(config[CONF_HEADER_HIGH])) + cg.add(var.set_header_low(config[CONF_HEADER_LOW])) + cg.add(var.set_bit_high(config[CONF_BIT_HIGH])) + cg.add(var.set_bit_one_low(config[CONF_BIT_ONE_LOW])) + cg.add(var.set_bit_zero_low(config[CONF_BIT_ZERO_LOW])) diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp index 80677e6de341..ee73d307963b 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.cpp +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -9,6 +9,7 @@ static const char *TAG = "climate.climate_ir_lg"; const uint32_t COMMAND_ON = 0x00000; const uint32_t COMMAND_ON_AI = 0x03000; const uint32_t COMMAND_COOL = 0x08000; +const uint32_t COMMAND_HEAT = 0x0C000; const uint32_t COMMAND_OFF = 0xC0000; const uint32_t COMMAND_SWING = 0x10000; // On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. @@ -28,13 +29,6 @@ const uint8_t TEMP_RANGE = TEMP_MAX - TEMP_MIN + 1; const uint32_t TEMP_MASK = 0XF00; const uint32_t TEMP_SHIFT = 8; -// Constants -static const uint32_t HEADER_HIGH_US = 8000; -static const uint32_t HEADER_LOW_US = 4000; -static const uint32_t BIT_HIGH_US = 600; -static const uint32_t BIT_ONE_LOW_US = 1600; -static const uint32_t BIT_ZERO_LOW_US = 550; - const uint16_t BITS = 28; void LgIrClimate::transmit_state() { @@ -55,6 +49,9 @@ void LgIrClimate::transmit_state() { case climate::CLIMATE_MODE_COOL: remote_state |= COMMAND_COOL; break; + case climate::CLIMATE_MODE_HEAT: + remote_state |= COMMAND_HEAT; + break; case climate::CLIMATE_MODE_AUTO: remote_state |= COMMAND_AUTO; break; @@ -73,7 +70,8 @@ void LgIrClimate::transmit_state() { if (this->mode == climate::CLIMATE_MODE_OFF) { remote_state |= FAN_AUTO; - } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY) { + } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY || + this->mode == climate::CLIMATE_MODE_HEAT) { switch (this->fan_mode) { case climate::CLIMATE_FAN_HIGH: remote_state |= FAN_MAX; @@ -95,7 +93,7 @@ void LgIrClimate::transmit_state() { this->fan_mode = climate::CLIMATE_FAN_AUTO; // remote_state |= FAN_MODE_AUTO_DRY; } - if (this->mode == climate::CLIMATE_MODE_COOL) { + if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) { auto temp = (uint8_t) roundf(clamp(this->target_temperature, TEMP_MIN, TEMP_MAX)); remote_state |= ((temp - 15) << TEMP_SHIFT); } @@ -108,13 +106,13 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { uint8_t nbits = 0; uint32_t remote_state = 0; - if (!data.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) + if (!data.expect_item(this->header_high_, this->header_low_)) return false; for (nbits = 0; nbits < 32; nbits++) { - if (data.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + if (data.expect_item(this->bit_high_, this->bit_one_low_)) { remote_state = (remote_state << 1) | 1; - } else if (data.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + } else if (data.expect_item(this->bit_high_, this->bit_zero_low_)) { remote_state = (remote_state << 1) | 0; } else if (nbits == BITS) { break; @@ -141,29 +139,32 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { } else { if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) this->mode = climate::CLIMATE_MODE_AUTO; - else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) { + else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) this->mode = climate::CLIMATE_MODE_DRY; + else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { + this->mode = climate::CLIMATE_MODE_HEAT; } else { this->mode = climate::CLIMATE_MODE_COOL; } - } - // Temperature - if (this->mode == climate::CLIMATE_MODE_COOL) - this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15; + // Temperature + if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) + this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15; - // Fan Speed - if (this->mode == climate::CLIMATE_MODE_AUTO) { - this->fan_mode = climate::CLIMATE_FAN_AUTO; - } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY) { - if ((remote_state & FAN_MASK) == FAN_AUTO) + // Fan Speed + if (this->mode == climate::CLIMATE_MODE_AUTO) { this->fan_mode = climate::CLIMATE_FAN_AUTO; - else if ((remote_state & FAN_MASK) == FAN_MIN) - this->fan_mode = climate::CLIMATE_FAN_LOW; - else if ((remote_state & FAN_MASK) == FAN_MED) - this->fan_mode = climate::CLIMATE_FAN_MEDIUM; - else if ((remote_state & FAN_MASK) == FAN_MAX) - this->fan_mode = climate::CLIMATE_FAN_HIGH; + } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT || + this->mode == climate::CLIMATE_MODE_DRY) { + if ((remote_state & FAN_MASK) == FAN_AUTO) + this->fan_mode = climate::CLIMATE_FAN_AUTO; + else if ((remote_state & FAN_MASK) == FAN_MIN) + this->fan_mode = climate::CLIMATE_FAN_LOW; + else if ((remote_state & FAN_MASK) == FAN_MED) + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + else if ((remote_state & FAN_MASK) == FAN_MAX) + this->fan_mode = climate::CLIMATE_FAN_HIGH; + } } this->publish_state(); @@ -179,15 +180,16 @@ void LgIrClimate::transmit_(uint32_t value) { data->set_carrier_frequency(38000); data->reserve(2 + BITS * 2u); - data->item(HEADER_HIGH_US, HEADER_LOW_US); + data->item(this->header_high_, this->header_low_); for (uint32_t mask = 1UL << (BITS - 1); mask != 0; mask >>= 1) { - if (value & mask) - data->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else - data->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + if (value & mask) { + data->item(this->bit_high_, this->bit_one_low_); + } else { + data->item(this->bit_high_, this->bit_zero_low_); + } } - data->mark(BIT_HIGH_US); + data->mark(this->bit_high_); transmit.perform(); } void LgIrClimate::calc_checksum_(uint32_t &value) { diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.h b/esphome/components/climate_ir_lg/climate_ir_lg.h index 204482e7a874..6b38b3247b50 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.h +++ b/esphome/components/climate_ir_lg/climate_ir_lg.h @@ -25,6 +25,11 @@ class LgIrClimate : public climate_ir::ClimateIR { this->swing_mode = climate::CLIMATE_SWING_OFF; climate_ir::ClimateIR::control(call); } + void set_header_high(uint32_t header_high) { this->header_high_ = header_high; } + void set_header_low(uint32_t header_low) { this->header_low_ = header_low; } + void set_bit_high(uint32_t bit_high) { this->bit_high_ = bit_high; } + void set_bit_one_low(uint32_t bit_one_low) { this->bit_one_low_ = bit_one_low; } + void set_bit_zero_low(uint32_t bit_zero_low) { this->bit_zero_low_ = bit_zero_low; } protected: /// Transmit via IR the state of this climate controller. @@ -37,6 +42,12 @@ class LgIrClimate : public climate_ir::ClimateIR { void calc_checksum_(uint32_t &value); void transmit_(uint32_t value); + uint32_t header_high_; + uint32_t header_low_; + uint32_t bit_high_; + uint32_t bit_one_low_; + uint32_t bit_zero_low_; + climate::ClimateMode mode_before_{climate::CLIMATE_MODE_OFF}; }; diff --git a/esphome/components/color/__init__.py b/esphome/components/color/__init__.py index 3e2e7b2c07ef..6712d078a414 100644 --- a/esphome/components/color/__init__.py +++ b/esphome/components/color/__init__.py @@ -2,22 +2,56 @@ from esphome import codegen as cg from esphome.const import CONF_BLUE, CONF_GREEN, CONF_ID, CONF_RED, CONF_WHITE -ColorStruct = cg.esphome_ns.struct('Color') +ColorStruct = cg.esphome_ns.struct("Color") MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(ColorStruct), - cv.Optional(CONF_RED, default=0.0): cv.percentage, - cv.Optional(CONF_GREEN, default=0.0): cv.percentage, - cv.Optional(CONF_BLUE, default=0.0): cv.percentage, - cv.Optional(CONF_WHITE, default=0.0): cv.percentage, -}).extend(cv.COMPONENT_SCHEMA) + +CONF_RED_INT = "red_int" +CONF_GREEN_INT = "green_int" +CONF_BLUE_INT = "blue_int" +CONF_WHITE_INT = "white_int" + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(ColorStruct), + cv.Exclusive(CONF_RED, "red"): cv.percentage, + cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t, + cv.Exclusive(CONF_GREEN, "green"): cv.percentage, + cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t, + cv.Exclusive(CONF_BLUE, "blue"): cv.percentage, + cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t, + cv.Exclusive(CONF_WHITE, "white"): cv.percentage, + cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): - cg.variable(config[CONF_ID], cg.StructInitializer( - ColorStruct, - ('r', config[CONF_RED]), - ('g', config[CONF_GREEN]), - ('b', config[CONF_BLUE]), - ('w', config[CONF_WHITE]))) + r = 0 + if CONF_RED in config: + r = int(config[CONF_RED] * 255) + elif CONF_RED_INT in config: + r = config[CONF_RED_INT] + + g = 0 + if CONF_GREEN in config: + g = int(config[CONF_GREEN] * 255) + elif CONF_GREEN_INT in config: + g = config[CONF_GREEN_INT] + + b = 0 + if CONF_BLUE in config: + b = int(config[CONF_BLUE] * 255) + elif CONF_BLUE_INT in config: + b = config[CONF_BLUE_INT] + + w = 0 + if CONF_WHITE in config: + w = int(config[CONF_WHITE] * 255) + elif CONF_WHITE_INT in config: + w = config[CONF_WHITE_INT] + + cg.new_variable( + config[CONF_ID], + cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)), + ) diff --git a/esphome/components/coolix/climate.py b/esphome/components/coolix/climate.py index 075fad5a4bc7..b7a584643b6c 100644 --- a/esphome/components/coolix/climate.py +++ b/esphome/components/coolix/climate.py @@ -3,15 +3,17 @@ from esphome.components import climate_ir from esphome.const import CONF_ID -AUTO_LOAD = ['climate_ir'] -CODEOWNERS = ['@glmnet'] +AUTO_LOAD = ["climate_ir"] +CODEOWNERS = ["@glmnet"] -coolix_ns = cg.esphome_ns.namespace('coolix') -CoolixClimate = coolix_ns.class_('CoolixClimate', climate_ir.ClimateIR) +coolix_ns = cg.esphome_ns.namespace("coolix") +CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(CoolixClimate), -}) +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CoolixClimate), + } +) def to_code(config): diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index f39e7ba540f2..e731c1833338 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -3,54 +3,74 @@ from esphome import automation from esphome.automation import maybe_simple_id, Condition from esphome.components import mqtt -from esphome.const import CONF_ID, CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, \ - CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID, CONF_NAME +from esphome.const import ( + CONF_ID, + CONF_INTERNAL, + CONF_DEVICE_CLASS, + CONF_STATE, + CONF_POSITION, + CONF_TILT, + CONF_STOP, + CONF_MQTT_ID, + CONF_NAME, +) from esphome.core import CORE, coroutine, coroutine_with_priority IS_PLATFORM_COMPONENT = True -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] DEVICE_CLASSES = [ - '', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage', - 'gate', 'shade', 'shutter', 'window' + "", + "awning", + "blind", + "curtain", + "damper", + "door", + "garage", + "gate", + "shade", + "shutter", + "window", ] -cover_ns = cg.esphome_ns.namespace('cover') +cover_ns = cg.esphome_ns.namespace("cover") -Cover = cover_ns.class_('Cover', cg.Nameable) +Cover = cover_ns.class_("Cover", cg.Nameable) COVER_OPEN = cover_ns.COVER_OPEN COVER_CLOSED = cover_ns.COVER_CLOSED COVER_STATES = { - 'OPEN': COVER_OPEN, - 'CLOSED': COVER_CLOSED, + "OPEN": COVER_OPEN, + "CLOSED": COVER_CLOSED, } validate_cover_state = cv.enum(COVER_STATES, upper=True) -CoverOperation = cover_ns.enum('CoverOperation') +CoverOperation = cover_ns.enum("CoverOperation") COVER_OPERATIONS = { - 'IDLE': CoverOperation.COVER_OPERATION_IDLE, - 'OPENING': CoverOperation.COVER_OPERATION_OPENING, - 'CLOSING': CoverOperation.COVER_OPERATION_CLOSING, + "IDLE": CoverOperation.COVER_OPERATION_IDLE, + "OPENING": CoverOperation.COVER_OPERATION_OPENING, + "CLOSING": CoverOperation.COVER_OPERATION_CLOSING, } validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True) # Actions -OpenAction = cover_ns.class_('OpenAction', automation.Action) -CloseAction = cover_ns.class_('CloseAction', automation.Action) -StopAction = cover_ns.class_('StopAction', automation.Action) -ControlAction = cover_ns.class_('ControlAction', automation.Action) -CoverPublishAction = cover_ns.class_('CoverPublishAction', automation.Action) -CoverIsOpenCondition = cover_ns.class_('CoverIsOpenCondition', Condition) -CoverIsClosedCondition = cover_ns.class_('CoverIsClosedCondition', Condition) - -COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(Cover), - cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTCoverComponent), - cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), - # TODO: MQTT topic options -}) +OpenAction = cover_ns.class_("OpenAction", automation.Action) +CloseAction = cover_ns.class_("CloseAction", automation.Action) +StopAction = cover_ns.class_("StopAction", automation.Action) +ControlAction = cover_ns.class_("ControlAction", automation.Action) +CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) +CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) +CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) + +COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Cover), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), + cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), + # TODO: MQTT topic options + } +) @coroutine @@ -74,39 +94,43 @@ def register_cover(var, config): yield setup_cover_core_(var, config) -COVER_ACTION_SCHEMA = maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(Cover), -}) +COVER_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Cover), + } +) -@automation.register_action('cover.open', OpenAction, COVER_ACTION_SCHEMA) +@automation.register_action("cover.open", OpenAction, COVER_ACTION_SCHEMA) def cover_open_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_action('cover.close', CloseAction, COVER_ACTION_SCHEMA) +@automation.register_action("cover.close", CloseAction, COVER_ACTION_SCHEMA) def cover_close_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_action('cover.stop', StopAction, COVER_ACTION_SCHEMA) +@automation.register_action("cover.stop", StopAction, COVER_ACTION_SCHEMA) def cover_stop_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -COVER_CONTROL_ACTION_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.use_id(Cover), - cv.Optional(CONF_STOP): cv.templatable(cv.boolean), - cv.Exclusive(CONF_STATE, 'pos'): cv.templatable(validate_cover_state), - cv.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.percentage), - cv.Optional(CONF_TILT): cv.templatable(cv.percentage), -}) +COVER_CONTROL_ACTION_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Cover), + cv.Optional(CONF_STOP): cv.templatable(cv.boolean), + cv.Exclusive(CONF_STATE, "pos"): cv.templatable(validate_cover_state), + cv.Exclusive(CONF_POSITION, "pos"): cv.templatable(cv.percentage), + cv.Optional(CONF_TILT): cv.templatable(cv.percentage), + } +) -@automation.register_action('cover.control', ControlAction, COVER_CONTROL_ACTION_SCHEMA) +@automation.register_action("cover.control", ControlAction, COVER_CONTROL_ACTION_SCHEMA) def cover_control_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -127,5 +151,5 @@ def cover_control_to_code(config, action_id, template_arg, args): @coroutine_with_priority(100.0) def to_code(config): - cg.add_define('USE_COVER') + cg.add_define("USE_COVER") cg.add_global(cover_ns.using) diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index a415d6768888..71e9458618de 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -1,21 +1,45 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, uart -from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \ - UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT +from esphome.const import ( + CONF_CURRENT, + CONF_ID, + CONF_POWER, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, +) -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] -cse7766_ns = cg.esphome_ns.namespace('cse7766') -CSE7766Component = cse7766_ns.class_('CSE7766Component', cg.PollingComponent, uart.UARTDevice) +cse7766_ns = cg.esphome_ns.namespace("cse7766") +CSE7766Component = cse7766_ns.class_( + "CSE7766Component", cg.PollingComponent, uart.UARTDevice +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(CSE7766Component), - - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), - cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), - cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), -}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CSE7766Component), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): diff --git a/esphome/components/ct_clamp/sensor.py b/esphome/components/ct_clamp/sensor.py index 42a3b6649785..e4dbd923870b 100644 --- a/esphome/components/ct_clamp/sensor.py +++ b/esphome/components/ct_clamp/sensor.py @@ -1,21 +1,35 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, voltage_sampler -from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE +from esphome.const import ( + CONF_SENSOR, + CONF_ID, + DEVICE_CLASS_CURRENT, + ICON_EMPTY, + UNIT_AMPERE, +) -AUTO_LOAD = ['voltage_sampler'] -CODEOWNERS = ['@jesserockz'] +AUTO_LOAD = ["voltage_sampler"] +CODEOWNERS = ["@jesserockz"] -CONF_SAMPLE_DURATION = 'sample_duration' +CONF_SAMPLE_DURATION = "sample_duration" -ct_clamp_ns = cg.esphome_ns.namespace('ct_clamp') -CTClampSensor = ct_clamp_ns.class_('CTClampSensor', sensor.Sensor, cg.PollingComponent) +ct_clamp_ns = cg.esphome_ns.namespace("ct_clamp") +CTClampSensor = ct_clamp_ns.class_("CTClampSensor", sensor.Sensor, cg.PollingComponent) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2).extend({ - cv.GenerateID(): cv.declare_id(CTClampSensor), - cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), - cv.Optional(CONF_SAMPLE_DURATION, default='200ms'): cv.positive_time_period_milliseconds, -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT) + .extend( + { + cv.GenerateID(): cv.declare_id(CTClampSensor), + cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), + cv.Optional( + CONF_SAMPLE_DURATION, default="200ms" + ): cv.positive_time_period_milliseconds, + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): diff --git a/esphome/components/custom/__init__.py b/esphome/components/custom/__init__.py index 0ef87cc6e145..74450300f39f 100644 --- a/esphome/components/custom/__init__.py +++ b/esphome/components/custom/__init__.py @@ -1,3 +1,3 @@ import esphome.codegen as cg -custom_ns = cg.esphome_ns.namespace('custom') +custom_ns = cg.esphome_ns.namespace("custom") diff --git a/esphome/components/custom/binary_sensor/__init__.py b/esphome/components/custom/binary_sensor/__init__.py index 83b4a3dad886..402540c254cd 100644 --- a/esphome/components/custom/binary_sensor/__init__.py +++ b/esphome/components/custom/binary_sensor/__init__.py @@ -4,18 +4,25 @@ from esphome.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA from .. import custom_ns -CustomBinarySensorConstructor = custom_ns.class_('CustomBinarySensorConstructor') +CustomBinarySensorConstructor = custom_ns.class_("CustomBinarySensorConstructor") -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_BINARY_SENSORS): cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA), -}) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor), + cv.Required(CONF_LAMBDA): cv.returning_lambda, + cv.Required(CONF_BINARY_SENSORS): cv.ensure_list( + binary_sensor.BINARY_SENSOR_SCHEMA + ), + } +) def to_code(config): template_ = yield cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.std_vector.template(binary_sensor.BinarySensorPtr)) + config[CONF_LAMBDA], + [], + return_type=cg.std_vector.template(binary_sensor.BinarySensorPtr), + ) rhs = CustomBinarySensorConstructor(template_) custom = cg.variable(config[CONF_ID], rhs) diff --git a/esphome/components/custom/climate/__init__.py b/esphome/components/custom/climate/__init__.py index ed19452deed4..75f9c3247e95 100644 --- a/esphome/components/custom/climate/__init__.py +++ b/esphome/components/custom/climate/__init__.py @@ -4,20 +4,24 @@ from esphome.const import CONF_ID, CONF_LAMBDA from .. import custom_ns -CustomClimateConstructor = custom_ns.class_('CustomClimateConstructor') -CONF_CLIMATES = 'climates' +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), -}) +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'))) + config[CONF_LAMBDA], + [], + return_type=cg.std_vector.template(climate.Climate.operator("ptr")), + ) rhs = CustomClimateConstructor(template_) custom = cg.variable(config[CONF_ID], rhs) diff --git a/esphome/components/custom/cover/__init__.py b/esphome/components/custom/cover/__init__.py index 3ab3ec1d042b..35f25b827db4 100644 --- a/esphome/components/custom/cover/__init__.py +++ b/esphome/components/custom/cover/__init__.py @@ -4,20 +4,24 @@ from esphome.const import CONF_ID, CONF_LAMBDA from .. import custom_ns -CustomCoverConstructor = custom_ns.class_('CustomCoverConstructor') -CONF_COVERS = 'covers' +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), -}) +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'))) + config[CONF_LAMBDA], + [], + return_type=cg.std_vector.template(cover.Cover.operator("ptr")), + ) rhs = CustomCoverConstructor(template_) custom = cg.variable(config[CONF_ID], rhs) diff --git a/esphome/components/custom/light/__init__.py b/esphome/components/custom/light/__init__.py index 61dd74e661a7..f4bd8331f131 100644 --- a/esphome/components/custom/light/__init__.py +++ b/esphome/components/custom/light/__init__.py @@ -4,20 +4,24 @@ from esphome.const import CONF_ID, CONF_LAMBDA from .. import custom_ns -CustomLightOutputConstructor = custom_ns.class_('CustomLightOutputConstructor') -CONF_LIGHTS = 'lights' +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.ADDRESSABLE_LIGHT_SCHEMA), -}) +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.ADDRESSABLE_LIGHT_SCHEMA), + } +) def to_code(config): template_ = yield cg.process_lambda( - config[CONF_LAMBDA], [], - return_type=cg.std_vector.template(light.LightOutput.operator('ptr'))) + config[CONF_LAMBDA], + [], + return_type=cg.std_vector.template(light.LightOutput.operator("ptr")), + ) rhs = CustomLightOutputConstructor(template_) custom = cg.variable(config[CONF_ID], rhs) diff --git a/esphome/components/custom/output/__init__.py b/esphome/components/custom/output/__init__.py index efe6f19dabc0..c803d89c3231 100644 --- a/esphome/components/custom/output/__init__.py +++ b/esphome/components/custom/output/__init__.py @@ -4,41 +4,55 @@ from esphome.const import CONF_ID, CONF_LAMBDA, CONF_OUTPUTS, CONF_TYPE, CONF_BINARY from .. import custom_ns -CustomBinaryOutputConstructor = custom_ns.class_('CustomBinaryOutputConstructor') -CustomFloatOutputConstructor = custom_ns.class_('CustomFloatOutputConstructor') +CustomBinaryOutputConstructor = custom_ns.class_("CustomBinaryOutputConstructor") +CustomFloatOutputConstructor = custom_ns.class_("CustomFloatOutputConstructor") -CONF_FLOAT = 'float' +CONF_FLOAT = "float" -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) +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): type = config[CONF_TYPE] - if type == 'binary': + if type == "binary": ret_type = output.BinaryOutputPtr klass = CustomBinaryOutputConstructor else: ret_type = output.FloatOutputPtr klass = CustomFloatOutputConstructor - template_ = yield cg.process_lambda(config[CONF_LAMBDA], [], - return_type=cg.std_vector.template(ret_type)) + template_ = yield cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.std_vector.template(ret_type) + ) rhs = klass(template_) custom = cg.variable(config[CONF_ID], rhs) diff --git a/esphome/components/custom/sensor/__init__.py b/esphome/components/custom/sensor/__init__.py index e6da4a733cdd..2be14afc0d4b 100644 --- a/esphome/components/custom/sensor/__init__.py +++ b/esphome/components/custom/sensor/__init__.py @@ -4,18 +4,21 @@ from esphome.const import CONF_ID, CONF_LAMBDA, CONF_SENSORS from .. import custom_ns -CustomSensorConstructor = custom_ns.class_('CustomSensorConstructor') +CustomSensorConstructor = custom_ns.class_("CustomSensorConstructor") -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(CustomSensorConstructor), - cv.Required(CONF_LAMBDA): cv.returning_lambda, - cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA), -}) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CustomSensorConstructor), + cv.Required(CONF_LAMBDA): cv.returning_lambda, + cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA), + } +) def to_code(config): template_ = yield cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.std_vector.template(sensor.SensorPtr)) + config[CONF_LAMBDA], [], return_type=cg.std_vector.template(sensor.SensorPtr) + ) rhs = CustomSensorConstructor(template_) var = cg.variable(config[CONF_ID], rhs) diff --git a/esphome/components/custom/switch/__init__.py b/esphome/components/custom/switch/__init__.py index fd619e676999..a470c3e44028 100644 --- a/esphome/components/custom/switch/__init__.py +++ b/esphome/components/custom/switch/__init__.py @@ -4,21 +4,27 @@ from esphome.const import CONF_ID, CONF_LAMBDA, CONF_SWITCHES from .. import custom_ns -CustomSwitchConstructor = custom_ns.class_('CustomSwitchConstructor') +CustomSwitchConstructor = custom_ns.class_("CustomSwitchConstructor") -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(CustomSwitchConstructor), - 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), - })), -}) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CustomSwitchConstructor), + 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), + } + ) + ), + } +) def to_code(config): template_ = yield cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.std_vector.template(switch.SwitchPtr)) + config[CONF_LAMBDA], [], return_type=cg.std_vector.template(switch.SwitchPtr) + ) rhs = CustomSwitchConstructor(template_) var = cg.variable(config[CONF_ID], rhs) diff --git a/esphome/components/custom/text_sensor/__init__.py b/esphome/components/custom/text_sensor/__init__.py index 58db2a3f9ee2..f09d72d22883 100644 --- a/esphome/components/custom/text_sensor/__init__.py +++ b/esphome/components/custom/text_sensor/__init__.py @@ -4,21 +4,29 @@ from esphome.const import CONF_ID, CONF_LAMBDA, CONF_TEXT_SENSORS from .. import custom_ns -CustomTextSensorConstructor = custom_ns.class_('CustomTextSensorConstructor') +CustomTextSensorConstructor = custom_ns.class_("CustomTextSensorConstructor") -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor), - 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), - })), -}) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor), + 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), + } + ) + ), + } +) def to_code(config): template_ = yield cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.std_vector.template(text_sensor.TextSensorPtr)) + config[CONF_LAMBDA], + [], + return_type=cg.std_vector.template(text_sensor.TextSensorPtr), + ) rhs = CustomTextSensorConstructor(template_) var = cg.variable(config[CONF_ID], rhs) diff --git a/esphome/components/custom_component/__init__.py b/esphome/components/custom_component/__init__.py index 198aa6a9eceb..fc154a82c34b 100644 --- a/esphome/components/custom_component/__init__.py +++ b/esphome/components/custom_component/__init__.py @@ -2,22 +2,27 @@ import esphome.config_validation as cv from esphome.const import CONF_COMPONENTS, CONF_ID, CONF_LAMBDA -custom_component_ns = cg.esphome_ns.namespace('custom_component') -CustomComponentConstructor = custom_component_ns.class_('CustomComponentConstructor') +custom_component_ns = cg.esphome_ns.namespace("custom_component") +CustomComponentConstructor = custom_component_ns.class_("CustomComponentConstructor") MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(CustomComponentConstructor), - 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)), -}) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CustomComponentConstructor), + 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 + ) + ), + } +) def to_code(config): template_ = yield cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.std_vector.template(cg.ComponentPtr)) + config[CONF_LAMBDA], [], return_type=cg.std_vector.template(cg.ComponentPtr) + ) rhs = CustomComponentConstructor(template_) var = cg.variable(config[CONF_ID], rhs) diff --git a/esphome/components/cwww/light.py b/esphome/components/cwww/light.py index 5cc4262105d7..b099de435d3c 100644 --- a/esphome/components/cwww/light.py +++ b/esphome/components/cwww/light.py @@ -1,22 +1,29 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light, output -from esphome.const import CONF_OUTPUT_ID, CONF_COLD_WHITE, CONF_WARM_WHITE, \ - CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_COLD_WHITE, + CONF_WARM_WHITE, + CONF_COLD_WHITE_COLOR_TEMPERATURE, + CONF_WARM_WHITE_COLOR_TEMPERATURE, +) -cwww_ns = cg.esphome_ns.namespace('cwww') -CWWWLightOutput = cwww_ns.class_('CWWWLightOutput', light.LightOutput) +cwww_ns = cg.esphome_ns.namespace("cwww") +CWWWLightOutput = cwww_ns.class_("CWWWLightOutput", light.LightOutput) -CONF_CONSTANT_BRIGHTNESS = 'constant_brightness' +CONF_CONSTANT_BRIGHTNESS = "constant_brightness" -CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CWWWLightOutput), - cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), - cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), - cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, - cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, - cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, -}) +CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CWWWLightOutput), + cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), + cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), + cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, + } +) def to_code(config): diff --git a/esphome/components/daikin/climate.py b/esphome/components/daikin/climate.py index ff3f506fb20b..07bc079a9a73 100644 --- a/esphome/components/daikin/climate.py +++ b/esphome/components/daikin/climate.py @@ -3,14 +3,16 @@ from esphome.components import climate_ir from esphome.const import CONF_ID -AUTO_LOAD = ['climate_ir'] +AUTO_LOAD = ["climate_ir"] -daikin_ns = cg.esphome_ns.namespace('daikin') -DaikinClimate = daikin_ns.class_('DaikinClimate', climate_ir.ClimateIR) +daikin_ns = cg.esphome_ns.namespace("daikin") +DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(DaikinClimate), -}) +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(DaikinClimate), + } +) def to_code(config): diff --git a/esphome/components/dallas/__init__.py b/esphome/components/dallas/__init__.py index 85ab4300ee40..87049b8c64a5 100644 --- a/esphome/components/dallas/__init__.py +++ b/esphome/components/dallas/__init__.py @@ -4,18 +4,20 @@ from esphome.const import CONF_ID, CONF_PIN MULTI_CONF = True -AUTO_LOAD = ['sensor'] +AUTO_LOAD = ["sensor"] -CONF_ONE_WIRE_ID = 'one_wire_id' -dallas_ns = cg.esphome_ns.namespace('dallas') -DallasComponent = dallas_ns.class_('DallasComponent', cg.PollingComponent) -ESPOneWire = dallas_ns.class_('ESPOneWire') +CONF_ONE_WIRE_ID = "one_wire_id" +dallas_ns = cg.esphome_ns.namespace("dallas") +DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent) +ESPOneWire = dallas_ns.class_("ESPOneWire") -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(DallasComponent), - cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), - cv.Required(CONF_PIN): pins.gpio_input_pin_schema, -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(DallasComponent), + cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), + cv.Required(CONF_PIN): pins.gpio_input_pin_schema, + } +).extend(cv.polling_component_schema("60s")) def to_code(config): diff --git a/esphome/components/dallas/sensor.py b/esphome/components/dallas/sensor.py index df9d56199519..bb0a463a629b 100644 --- a/esphome/components/dallas/sensor.py +++ b/esphome/components/dallas/sensor.py @@ -1,20 +1,32 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import CONF_ADDRESS, CONF_DALLAS_ID, CONF_INDEX, CONF_RESOLUTION, UNIT_CELSIUS, \ - ICON_THERMOMETER, CONF_ID +from esphome.const import ( + CONF_ADDRESS, + CONF_DALLAS_ID, + CONF_INDEX, + CONF_RESOLUTION, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + CONF_ID, +) from . import DallasComponent, dallas_ns -DallasTemperatureSensor = dallas_ns.class_('DallasTemperatureSensor', sensor.Sensor) +DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sensor) -CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.GenerateID(): cv.declare_id(DallasTemperatureSensor), - cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent), - - cv.Optional(CONF_ADDRESS): cv.hex_int, - cv.Optional(CONF_INDEX): cv.positive_int, - cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12), -}), cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX)) +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE).extend( + { + cv.GenerateID(): cv.declare_id(DallasTemperatureSensor), + cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent), + cv.Optional(CONF_ADDRESS): cv.hex_int, + cv.Optional(CONF_INDEX): cv.positive_int, + cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12), + } + ), + cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX), +) def to_code(config): diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index d43b0de06ad1..e43eb8bfc1bf 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -2,14 +2,16 @@ import esphome.codegen as cg from esphome.const import CONF_ID -CODEOWNERS = ['@OttoWinter'] -DEPENDENCIES = ['logger'] +CODEOWNERS = ["@OttoWinter"] +DEPENDENCIES = ["logger"] -debug_ns = cg.esphome_ns.namespace('debug') -DebugComponent = debug_ns.class_('DebugComponent', cg.Component) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(DebugComponent), -}).extend(cv.COMPONENT_SCHEMA) +debug_ns = cg.esphome_ns.namespace("debug") +DebugComponent = debug_ns.class_("DebugComponent", cg.Component) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(DebugComponent), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 1b766c992803..793d6b2ebbe7 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -1,58 +1,81 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins, automation -from esphome.const import CONF_ID, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_CYCLES, \ - CONF_RUN_DURATION, CONF_SLEEP_DURATION, CONF_WAKEUP_PIN +from esphome.const import ( + CONF_ID, + CONF_MODE, + CONF_NUMBER, + CONF_PINS, + CONF_RUN_CYCLES, + CONF_RUN_DURATION, + CONF_SLEEP_DURATION, + CONF_WAKEUP_PIN, +) def validate_pin_number(value): valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39] if value[CONF_NUMBER] not in valid_pins: - raise cv.Invalid("Only pins {} support wakeup" - "".format(', '.join(str(x) for x in valid_pins))) + raise cv.Invalid( + "Only pins {} support wakeup" + "".format(", ".join(str(x) for x in valid_pins)) + ) return value -deep_sleep_ns = cg.esphome_ns.namespace('deep_sleep') -DeepSleepComponent = deep_sleep_ns.class_('DeepSleepComponent', cg.Component) -EnterDeepSleepAction = deep_sleep_ns.class_('EnterDeepSleepAction', automation.Action) -PreventDeepSleepAction = deep_sleep_ns.class_('PreventDeepSleepAction', automation.Action) +deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") +DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component) +EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action) +PreventDeepSleepAction = deep_sleep_ns.class_( + "PreventDeepSleepAction", automation.Action +) -WakeupPinMode = deep_sleep_ns.enum('WakeupPinMode') +WakeupPinMode = deep_sleep_ns.enum("WakeupPinMode") WAKEUP_PIN_MODES = { - 'IGNORE': WakeupPinMode.WAKEUP_PIN_MODE_IGNORE, - 'KEEP_AWAKE': WakeupPinMode.WAKEUP_PIN_MODE_KEEP_AWAKE, - 'INVERT_WAKEUP': WakeupPinMode.WAKEUP_PIN_MODE_INVERT_WAKEUP, + "IGNORE": WakeupPinMode.WAKEUP_PIN_MODE_IGNORE, + "KEEP_AWAKE": WakeupPinMode.WAKEUP_PIN_MODE_KEEP_AWAKE, + "INVERT_WAKEUP": WakeupPinMode.WAKEUP_PIN_MODE_INVERT_WAKEUP, } -esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum('esp_sleep_ext1_wakeup_mode_t') -Ext1Wakeup = deep_sleep_ns.struct('Ext1Wakeup') +esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum("esp_sleep_ext1_wakeup_mode_t") +Ext1Wakeup = deep_sleep_ns.struct("Ext1Wakeup") EXT1_WAKEUP_MODES = { - 'ALL_LOW': esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW, - 'ANY_HIGH': esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH, + "ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW, + "ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH, } -CONF_WAKEUP_PIN_MODE = 'wakeup_pin_mode' -CONF_ESP32_EXT1_WAKEUP = 'esp32_ext1_wakeup' - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(DeepSleepComponent), - cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds, - - cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, - cv.Optional(CONF_WAKEUP_PIN): cv.All(cv.only_on_esp32, pins.internal_gpio_input_pin_schema, - validate_pin_number), - cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All(cv.only_on_esp32, - cv.enum(WAKEUP_PIN_MODES), upper=True), - cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(cv.only_on_esp32, cv.Schema({ - cv.Required(CONF_PINS): cv.ensure_list(pins.shorthand_input_pin, validate_pin_number), - cv.Required(CONF_MODE): cv.enum(EXT1_WAKEUP_MODES, upper=True), - })), - - cv.Optional(CONF_RUN_CYCLES): cv.invalid("The run_cycles option has been removed in 1.11.0 as " - "it was essentially the same as a run_duration of 0s." - "Please use run_duration now.") -}).extend(cv.COMPONENT_SCHEMA) +CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode" +CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(DeepSleepComponent), + cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds, + cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, + cv.Optional(CONF_WAKEUP_PIN): cv.All( + cv.only_on_esp32, pins.internal_gpio_input_pin_schema, validate_pin_number + ), + cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All( + cv.only_on_esp32, cv.enum(WAKEUP_PIN_MODES), upper=True + ), + cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All( + cv.only_on_esp32, + cv.Schema( + { + cv.Required(CONF_PINS): cv.ensure_list( + pins.shorthand_input_pin, validate_pin_number + ), + cv.Required(CONF_MODE): cv.enum(EXT1_WAKEUP_MODES, upper=True), + } + ), + ), + cv.Optional(CONF_RUN_CYCLES): cv.invalid( + "The run_cycles option has been removed in 1.11.0 as " + "it was essentially the same as a run_duration of 0s." + "Please use run_duration now." + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -75,27 +98,43 @@ def to_code(config): for pin in conf[CONF_PINS]: mask |= 1 << pin[CONF_NUMBER] struct = cg.StructInitializer( - Ext1Wakeup, - ('mask', mask), - ('wakeup_mode', conf[CONF_MODE]) + Ext1Wakeup, ("mask", mask), ("wakeup_mode", conf[CONF_MODE]) ) cg.add(var.set_ext1_wakeup(struct)) - cg.add_define('USE_DEEP_SLEEP') + cg.add_define("USE_DEEP_SLEEP") -DEEP_SLEEP_ACTION_SCHEMA = automation.maybe_simple_id({ - cv.GenerateID(): cv.use_id(DeepSleepComponent), -}) +DEEP_SLEEP_ENTER_SCHEMA = automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(DeepSleepComponent), + cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, + } +) -@automation.register_action('deep_sleep.enter', EnterDeepSleepAction, DEEP_SLEEP_ACTION_SCHEMA) +DEEP_SLEEP_PREVENT_SCHEMA = automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(DeepSleepComponent), + } +) + + +@automation.register_action( + "deep_sleep.enter", EnterDeepSleepAction, DEEP_SLEEP_ENTER_SCHEMA +) def deep_sleep_enter_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) - yield cg.new_Pvariable(action_id, template_arg, paren) + var = cg.new_Pvariable(action_id, template_arg, paren) + if CONF_SLEEP_DURATION in config: + template_ = yield cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32) + cg.add(var.set_sleep_duration(template_)) + yield var -@automation.register_action('deep_sleep.prevent', PreventDeepSleepAction, DEEP_SLEEP_ACTION_SCHEMA) +@automation.register_action( + "deep_sleep.prevent", PreventDeepSleepAction, DEEP_SLEEP_PREVENT_SCHEMA +) def deep_sleep_prevent_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 4372a3f66c6c..09212d7d1791 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -84,8 +84,14 @@ extern bool global_has_deep_sleep; template class EnterDeepSleepAction : public Action { public: EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {} - - void play(Ts... x) override { this->deep_sleep_->begin_sleep(true); } + TEMPLATABLE_VALUE(uint32_t, sleep_duration); + + void play(Ts... x) override { + if (this->sleep_duration_.has_value()) { + this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...)); + } + this->deep_sleep_->begin_sleep(true); + } protected: DeepSleepComponent *deep_sleep_; diff --git a/esphome/components/dfplayer/__init__.py b/esphome/components/dfplayer/__init__.py index 680a6b89ece4..33e74a220b14 100644 --- a/esphome/components/dfplayer/__init__.py +++ b/esphome/components/dfplayer/__init__.py @@ -4,57 +4,68 @@ from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE from esphome.components import uart -DEPENDENCIES = ['uart'] -CODEOWNERS = ['@glmnet'] - -dfplayer_ns = cg.esphome_ns.namespace('dfplayer') -DFPlayer = dfplayer_ns.class_('DFPlayer', cg.Component) -DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_('DFPlayerFinishedPlaybackTrigger', - automation.Trigger.template()) -DFPlayerIsPlayingCondition = dfplayer_ns.class_('DFPlayerIsPlayingCondition', automation.Condition) +DEPENDENCIES = ["uart"] +CODEOWNERS = ["@glmnet"] + +dfplayer_ns = cg.esphome_ns.namespace("dfplayer") +DFPlayer = dfplayer_ns.class_("DFPlayer", cg.Component) +DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_( + "DFPlayerFinishedPlaybackTrigger", automation.Trigger.template() +) +DFPlayerIsPlayingCondition = dfplayer_ns.class_( + "DFPlayerIsPlayingCondition", automation.Condition +) MULTI_CONF = True -CONF_FOLDER = 'folder' -CONF_LOOP = 'loop' -CONF_VOLUME = 'volume' -CONF_EQ_PRESET = 'eq_preset' -CONF_ON_FINISHED_PLAYBACK = 'on_finished_playback' +CONF_FOLDER = "folder" +CONF_LOOP = "loop" +CONF_VOLUME = "volume" +CONF_EQ_PRESET = "eq_preset" +CONF_ON_FINISHED_PLAYBACK = "on_finished_playback" EqPreset = dfplayer_ns.enum("EqPreset") EQ_PRESET = { - 'NORMAL': EqPreset.NORMAL, - 'POP': EqPreset.POP, - 'ROCK': EqPreset.ROCK, - 'JAZZ': EqPreset.JAZZ, - 'CLASSIC': EqPreset.CLASSIC, - 'BASS': EqPreset.BASS, + "NORMAL": EqPreset.NORMAL, + "POP": EqPreset.POP, + "ROCK": EqPreset.ROCK, + "JAZZ": EqPreset.JAZZ, + "CLASSIC": EqPreset.CLASSIC, + "BASS": EqPreset.BASS, } Device = dfplayer_ns.enum("Device") DEVICE = { - 'USB': Device.USB, - 'TF_CARD': Device.TF_CARD, + "USB": Device.USB, + "TF_CARD": Device.TF_CARD, } -NextAction = dfplayer_ns.class_('NextAction', automation.Action) -PreviousAction = dfplayer_ns.class_('PreviousAction', automation.Action) -PlayFileAction = dfplayer_ns.class_('PlayFileAction', automation.Action) -PlayFolderAction = dfplayer_ns.class_('PlayFolderAction', automation.Action) -SetVolumeAction = dfplayer_ns.class_('SetVolumeAction', automation.Action) -SetEqAction = dfplayer_ns.class_('SetEqAction', automation.Action) -SleepAction = dfplayer_ns.class_('SleepAction', automation.Action) -ResetAction = dfplayer_ns.class_('ResetAction', automation.Action) -StartAction = dfplayer_ns.class_('StartAction', automation.Action) -PauseAction = dfplayer_ns.class_('PauseAction', automation.Action) -StopAction = dfplayer_ns.class_('StopAction', automation.Action) -RandomAction = dfplayer_ns.class_('RandomAction', automation.Action) -SetDeviceAction = dfplayer_ns.class_('SetDeviceAction', automation.Action) - -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(DFPlayer), - cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DFPlayerFinishedPlaybackTrigger), - }), -}).extend(uart.UART_DEVICE_SCHEMA)) +NextAction = dfplayer_ns.class_("NextAction", automation.Action) +PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action) +PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action) +PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action) +SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action) +SetEqAction = dfplayer_ns.class_("SetEqAction", automation.Action) +SleepAction = dfplayer_ns.class_("SleepAction", automation.Action) +ResetAction = dfplayer_ns.class_("ResetAction", automation.Action) +StartAction = dfplayer_ns.class_("StartAction", automation.Action) +PauseAction = dfplayer_ns.class_("PauseAction", automation.Action) +StopAction = dfplayer_ns.class_("StopAction", automation.Action) +RandomAction = dfplayer_ns.class_("RandomAction", automation.Action) +SetDeviceAction = dfplayer_ns.class_("SetDeviceAction", automation.Action) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DFPlayer), + cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + DFPlayerFinishedPlaybackTrigger + ), + } + ), + } + ).extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): @@ -67,29 +78,48 @@ def to_code(config): yield automation.build_automation(trigger, [], conf) -@automation.register_action('dfplayer.play_next', NextAction, cv.Schema({ - cv.GenerateID(): cv.use_id(DFPlayer), -})) +@automation.register_action( + "dfplayer.play_next", + NextAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DFPlayer), + } + ), +) def dfplayer_next_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_action('dfplayer.play_previous', PreviousAction, cv.Schema({ - cv.GenerateID(): cv.use_id(DFPlayer), -})) +@automation.register_action( + "dfplayer.play_previous", + PreviousAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DFPlayer), + } + ), +) def dfplayer_previous_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_action('dfplayer.play', PlayFileAction, cv.maybe_simple_value({ - cv.GenerateID(): cv.use_id(DFPlayer), - cv.Required(CONF_FILE): cv.templatable(cv.int_), - cv.Optional(CONF_LOOP): cv.templatable(cv.boolean), -}, key=CONF_FILE)) +@automation.register_action( + "dfplayer.play", + PlayFileAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(DFPlayer), + cv.Required(CONF_FILE): cv.templatable(cv.int_), + cv.Optional(CONF_LOOP): cv.templatable(cv.boolean), + }, + key=CONF_FILE, + ), +) def dfplayer_play_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) @@ -101,12 +131,18 @@ def dfplayer_play_to_code(config, action_id, template_arg, args): yield var -@automation.register_action('dfplayer.play_folder', PlayFolderAction, cv.Schema({ - cv.GenerateID(): cv.use_id(DFPlayer), - cv.Required(CONF_FOLDER): cv.templatable(cv.int_), - cv.Optional(CONF_FILE): cv.templatable(cv.int_), - cv.Optional(CONF_LOOP): cv.templatable(cv.boolean), -})) +@automation.register_action( + "dfplayer.play_folder", + PlayFolderAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DFPlayer), + cv.Required(CONF_FOLDER): cv.templatable(cv.int_), + cv.Optional(CONF_FILE): cv.templatable(cv.int_), + cv.Optional(CONF_LOOP): cv.templatable(cv.boolean), + } + ), +) def dfplayer_play_folder_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) @@ -121,10 +157,17 @@ def dfplayer_play_folder_to_code(config, action_id, template_arg, args): yield var -@automation.register_action('dfplayer.set_device', SetDeviceAction, cv.maybe_simple_value({ - cv.GenerateID(): cv.use_id(DFPlayer), - cv.Required(CONF_DEVICE): cv.enum(DEVICE, upper=True), -}, key=CONF_DEVICE)) +@automation.register_action( + "dfplayer.set_device", + SetDeviceAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(DFPlayer), + cv.Required(CONF_DEVICE): cv.enum(DEVICE, upper=True), + }, + key=CONF_DEVICE, + ), +) def dfplayer_set_device_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) @@ -133,10 +176,17 @@ def dfplayer_set_device_to_code(config, action_id, template_arg, args): yield var -@automation.register_action('dfplayer.set_volume', SetVolumeAction, cv.maybe_simple_value({ - cv.GenerateID(): cv.use_id(DFPlayer), - cv.Required(CONF_VOLUME): cv.templatable(cv.int_), -}, key=CONF_VOLUME)) +@automation.register_action( + "dfplayer.set_volume", + SetVolumeAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(DFPlayer), + cv.Required(CONF_VOLUME): cv.templatable(cv.int_), + }, + key=CONF_VOLUME, + ), +) def dfplayer_set_volume_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) @@ -145,10 +195,17 @@ def dfplayer_set_volume_to_code(config, action_id, template_arg, args): yield var -@automation.register_action('dfplayer.set_eq', SetEqAction, cv.maybe_simple_value({ - cv.GenerateID(): cv.use_id(DFPlayer), - cv.Required(CONF_EQ_PRESET): cv.templatable(cv.enum(EQ_PRESET, upper=True)), -}, key=CONF_EQ_PRESET)) +@automation.register_action( + "dfplayer.set_eq", + SetEqAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(DFPlayer), + cv.Required(CONF_EQ_PRESET): cv.templatable(cv.enum(EQ_PRESET, upper=True)), + }, + key=CONF_EQ_PRESET, + ), +) def dfplayer_set_eq_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) @@ -157,63 +214,105 @@ def dfplayer_set_eq_to_code(config, action_id, template_arg, args): yield var -@automation.register_action('dfplayer.sleep', SleepAction, cv.Schema({ - cv.GenerateID(): cv.use_id(DFPlayer), -})) +@automation.register_action( + "dfplayer.sleep", + SleepAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DFPlayer), + } + ), +) def dfplayer_sleep_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_action('dfplayer.reset', ResetAction, cv.Schema({ - cv.GenerateID(): cv.use_id(DFPlayer), -})) +@automation.register_action( + "dfplayer.reset", + ResetAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DFPlayer), + } + ), +) def dfplayer_reset_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_action('dfplayer.start', StartAction, cv.Schema({ - cv.GenerateID(): cv.use_id(DFPlayer), -})) +@automation.register_action( + "dfplayer.start", + StartAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DFPlayer), + } + ), +) def dfplayer_start_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_action('dfplayer.pause', PauseAction, cv.Schema({ - cv.GenerateID(): cv.use_id(DFPlayer), -})) +@automation.register_action( + "dfplayer.pause", + PauseAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DFPlayer), + } + ), +) def dfplayer_pause_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_action('dfplayer.stop', StopAction, cv.Schema({ - cv.GenerateID(): cv.use_id(DFPlayer), -})) +@automation.register_action( + "dfplayer.stop", + StopAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DFPlayer), + } + ), +) def dfplayer_stop_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_action('dfplayer.random', RandomAction, cv.Schema({ - cv.GenerateID(): cv.use_id(DFPlayer), -})) +@automation.register_action( + "dfplayer.random", + RandomAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DFPlayer), + } + ), +) def dfplayer_random_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_condition('dfplayer.is_playing', DFPlayerIsPlayingCondition, cv.Schema({ - cv.GenerateID(): cv.use_id(DFPlayer), -})) +@automation.register_condition( + "dfplayer.is_playing", + DFPlayerIsPlayingCondition, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DFPlayer), + } + ), +) def dfplyaer_is_playing_to_code(config, condition_id, template_arg, args): var = cg.new_Pvariable(condition_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/dht/__init__.py b/esphome/components/dht/__init__.py index 6f14e1003304..71a87b6ae508 100644 --- a/esphome/components/dht/__init__.py +++ b/esphome/components/dht/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@OttoWinter'] +CODEOWNERS = ["@OttoWinter"] diff --git a/esphome/components/dht/sensor.py b/esphome/components/dht/sensor.py index 8749d85b523c..4b2f6ee8d2af 100644 --- a/esphome/components/dht/sensor.py +++ b/esphome/components/dht/sensor.py @@ -2,30 +2,49 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor -from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_MODEL, CONF_PIN, CONF_TEMPERATURE, \ - ICON_THERMOMETER, UNIT_CELSIUS, ICON_WATER_PERCENT, UNIT_PERCENT +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_MODEL, + CONF_PIN, + CONF_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, +) + from esphome.cpp_helpers import gpio_pin_expression -dht_ns = cg.esphome_ns.namespace('dht') -DHTModel = dht_ns.enum('DHTModel') +dht_ns = cg.esphome_ns.namespace("dht") +DHTModel = dht_ns.enum("DHTModel") DHT_MODELS = { - 'AUTO_DETECT': DHTModel.DHT_MODEL_AUTO_DETECT, - 'DHT11': DHTModel.DHT_MODEL_DHT11, - 'DHT22': DHTModel.DHT_MODEL_DHT22, - 'AM2302': DHTModel.DHT_MODEL_AM2302, - 'RHT03': DHTModel.DHT_MODEL_RHT03, - 'SI7021': DHTModel.DHT_MODEL_SI7021, - 'DHT22_TYPE2': DHTModel.DHT_MODEL_DHT22_TYPE2, + "AUTO_DETECT": DHTModel.DHT_MODEL_AUTO_DETECT, + "DHT11": DHTModel.DHT_MODEL_DHT11, + "DHT22": DHTModel.DHT_MODEL_DHT22, + "AM2302": DHTModel.DHT_MODEL_AM2302, + "RHT03": DHTModel.DHT_MODEL_RHT03, + "SI7021": DHTModel.DHT_MODEL_SI7021, + "DHT22_TYPE2": DHTModel.DHT_MODEL_DHT22_TYPE2, } -DHT = dht_ns.class_('DHT', cg.PollingComponent) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(DHT), - cv.Required(CONF_PIN): pins.gpio_input_pin_schema, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), - cv.Optional(CONF_MODEL, default='auto detect'): cv.enum(DHT_MODELS, upper=True, space='_'), -}).extend(cv.polling_component_schema('60s')) +DHT = dht_ns.class_("DHT", cg.PollingComponent) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(DHT), + cv.Required(CONF_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_MODEL, default="auto detect"): cv.enum( + DHT_MODELS, upper=True, space="_" + ), + } +).extend(cv.polling_component_schema("60s")) def to_code(config): diff --git a/esphome/components/dht12/sensor.py b/esphome/components/dht12/sensor.py index 7d86e8c836be..f63768142c87 100644 --- a/esphome/components/dht12/sensor.py +++ b/esphome/components/dht12/sensor.py @@ -1,19 +1,37 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + UNIT_CELSIUS, + ICON_EMPTY, + UNIT_PERCENT, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -dht12_ns = cg.esphome_ns.namespace('dht12') -DHT12Component = dht12_ns.class_('DHT12Component', cg.PollingComponent, i2c.I2CDevice) +dht12_ns = cg.esphome_ns.namespace("dht12") +DHT12Component = dht12_ns.class_("DHT12Component", cg.PollingComponent, i2c.I2CDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(DHT12Component), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DHT12Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x5C)) +) def to_code(config): diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 951d561caa74..f72ec88faea0 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -7,14 +7,18 @@ IS_PLATFORM_COMPONENT = True -display_ns = cg.esphome_ns.namespace('display') -DisplayBuffer = display_ns.class_('DisplayBuffer') -DisplayPage = display_ns.class_('DisplayPage') -DisplayPagePtr = DisplayPage.operator('ptr') -DisplayBufferRef = DisplayBuffer.operator('ref') -DisplayPageShowAction = display_ns.class_('DisplayPageShowAction', automation.Action) -DisplayPageShowNextAction = display_ns.class_('DisplayPageShowNextAction', automation.Action) -DisplayPageShowPrevAction = display_ns.class_('DisplayPageShowPrevAction', automation.Action) +display_ns = cg.esphome_ns.namespace("display") +DisplayBuffer = display_ns.class_("DisplayBuffer") +DisplayPage = display_ns.class_("DisplayPage") +DisplayPagePtr = DisplayPage.operator("ptr") +DisplayBufferRef = DisplayBuffer.operator("ref") +DisplayPageShowAction = display_ns.class_("DisplayPageShowAction", automation.Action) +DisplayPageShowNextAction = display_ns.class_( + "DisplayPageShowNextAction", automation.Action +) +DisplayPageShowPrevAction = display_ns.class_( + "DisplayPageShowPrevAction", automation.Action +) DISPLAY_ROTATIONS = { 0: display_ns.DISPLAY_ROTATION_0_DEGREES, @@ -31,17 +35,26 @@ def validate_rotation(value): return cv.enum(DISPLAY_ROTATIONS, int=True)(value) -BASIC_DISPLAY_SCHEMA = cv.Schema({ - cv.Optional(CONF_LAMBDA): cv.lambda_, -}) - -FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend({ - cv.Optional(CONF_ROTATION): validate_rotation, - cv.Optional(CONF_PAGES): cv.All(cv.ensure_list({ - cv.GenerateID(): cv.declare_id(DisplayPage), - cv.Required(CONF_LAMBDA): cv.lambda_, - }), cv.Length(min=1)), -}) +BASIC_DISPLAY_SCHEMA = cv.Schema( + { + cv.Optional(CONF_LAMBDA): cv.lambda_, + } +) + +FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend( + { + cv.Optional(CONF_ROTATION): validate_rotation, + cv.Optional(CONF_PAGES): cv.All( + cv.ensure_list( + { + cv.GenerateID(): cv.declare_id(DisplayPage), + cv.Required(CONF_LAMBDA): cv.lambda_, + } + ), + cv.Length(min=1), + ), + } +) @coroutine @@ -51,8 +64,9 @@ def setup_display_core_(var, config): if CONF_PAGES in config: pages = [] for conf in config[CONF_PAGES]: - lambda_ = yield cg.process_lambda(conf[CONF_LAMBDA], [(DisplayBufferRef, 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + conf[CONF_LAMBDA], [(DisplayBufferRef, "it")], return_type=cg.void + ) page = cg.new_Pvariable(conf[CONF_ID], lambda_) pages.append(page) cg.add(var.set_pages(pages)) @@ -63,9 +77,15 @@ def register_display(var, config): yield setup_display_core_(var, config) -@automation.register_action('display.page.show', DisplayPageShowAction, maybe_simple_id({ - cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayPage)), -})) +@automation.register_action( + "display.page.show", + DisplayPageShowAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayPage)), + } + ), +) def display_page_show_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) if isinstance(config[CONF_ID], core.Lambda): @@ -77,18 +97,29 @@ def display_page_show_to_code(config, action_id, template_arg, args): yield var -@automation.register_action('display.page.show_next', DisplayPageShowNextAction, maybe_simple_id({ - cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)), -})) +@automation.register_action( + "display.page.show_next", + DisplayPageShowNextAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)), + } + ), +) def display_page_show_next_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_action('display.page.show_previous', DisplayPageShowPrevAction, - maybe_simple_id({ - cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)), - })) +@automation.register_action( + "display.page.show_previous", + DisplayPageShowPrevAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)), + } + ), +) def display_page_show_previous_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 8b65ce72e13f..994997e6e0b1 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -9,7 +9,7 @@ namespace display { static const char *TAG = "display"; const Color COLOR_OFF(0, 0, 0, 0); -const Color COLOR_ON(1, 1, 1, 1); +const Color COLOR_ON(255, 255, 255, 255); void DisplayBuffer::init_internal_(uint32_t buffer_length) { this->buffer_ = new uint8_t[buffer_length]; diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 235224d42e9f..5a63441e2d13 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -3,7 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/automation.h" -#include "esphome/core/color.h" +#include "display_color_utils.h" #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h new file mode 100644 index 000000000000..8fc3b0adb9fb --- /dev/null +++ b/esphome/components/display/display_color_utils.h @@ -0,0 +1,110 @@ +#pragma once +#include "esphome/core/color.h" + +namespace esphome { +namespace display { +enum ColorOrder : uint8_t { COLOR_ORDER_RGB = 0, COLOR_ORDER_BGR = 1, COLOR_ORDER_GRB = 2 }; +enum ColorBitness : uint8_t { COLOR_BITNESS_888 = 0, COLOR_BITNESS_565 = 1, COLOR_BITNESS_332 = 2 }; +inline static uint8_t esp_scale(uint8_t i, uint8_t scale, uint8_t max_value = 255) { return (max_value * i / scale); } + +class ColorUtil { + public: + static Color to_color(uint32_t colorcode, ColorOrder color_order, + ColorBitness color_bitness = ColorBitness::COLOR_BITNESS_888, bool right_bit_aligned = true) { + uint8_t first_color, second_color, third_color; + uint8_t first_bits = 0; + uint8_t second_bits = 0; + uint8_t third_bits = 0; + + switch (color_bitness) { + case COLOR_BITNESS_888: + first_bits = 8; + second_bits = 8; + third_bits = 8; + break; + case COLOR_BITNESS_565: + first_bits = 5; + second_bits = 6; + third_bits = 5; + break; + case COLOR_BITNESS_332: + first_bits = 3; + second_bits = 3; + third_bits = 2; + break; + } + + first_color = right_bit_aligned ? esp_scale(((colorcode >> (second_bits + third_bits)) & ((1 << first_bits) - 1)), + ((1 << first_bits) - 1)) + : esp_scale(((colorcode >> 16) & 0xFF), (1 << first_bits) - 1); + + second_color = right_bit_aligned + ? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1)) + : esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1)); + + third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & 0xFF), ((1 << third_bits) - 1)) + : esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1)); + + Color color_return; + + switch (color_order) { + case COLOR_ORDER_RGB: + color_return.r = first_color; + color_return.g = second_color; + color_return.b = third_color; + break; + case COLOR_ORDER_BGR: + color_return.b = first_color; + color_return.g = second_color; + color_return.r = third_color; + break; + case COLOR_ORDER_GRB: + color_return.g = first_color; + color_return.r = second_color; + color_return.b = third_color; + break; + } + return color_return; + } + static uint8_t color_to_332(Color color, ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) { + uint16_t red_color, green_color, blue_color; + + red_color = esp_scale8(color.red, ((1 << 3) - 1)); + green_color = esp_scale8(color.green, ((1 << 3) - 1)); + blue_color = esp_scale8(color.blue, (1 << 2) - 1); + + switch (color_order) { + case COLOR_ORDER_RGB: + return red_color << 5 | green_color << 2 | blue_color; + case COLOR_ORDER_BGR: + return blue_color << 6 | green_color << 3 | red_color; + case COLOR_ORDER_GRB: + return green_color << 5 | red_color << 2 | blue_color; + } + return 0; + } + static uint16_t color_to_565(Color color, ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) { + uint16_t red_color, green_color, blue_color; + + red_color = esp_scale8(color.red, ((1 << 5) - 1)); + green_color = esp_scale8(color.green, ((1 << 6) - 1)); + blue_color = esp_scale8(color.blue, (1 << 5) - 1); + + switch (color_order) { + case COLOR_ORDER_RGB: + return red_color << 11 | green_color << 5 | blue_color; + case COLOR_ORDER_BGR: + return blue_color << 11 | green_color << 5 | red_color; + case COLOR_ORDER_GRB: + return green_color << 10 | red_color << 5 | blue_color; + } + return 0; + } + + static uint32_t color_to_grayscale4(Color color) { + uint32_t gs4 = esp_scale8(color.white, 15); + return gs4; + } +}; +} // namespace display +} // namespace esphome diff --git a/esphome/components/ds1307/time.py b/esphome/components/ds1307/time.py index 371dc85be874..9c1d75c029bc 100644 --- a/esphome/components/ds1307/time.py +++ b/esphome/components/ds1307/time.py @@ -5,31 +5,45 @@ from esphome.const import CONF_ID -CODEOWNERS = ['@badbadc0ffee'] -DEPENDENCIES = ['i2c'] -ds1307_ns = cg.esphome_ns.namespace('ds1307') -DS1307Component = ds1307_ns.class_('DS1307Component', time.RealTimeClock, i2c.I2CDevice) -WriteAction = ds1307_ns.class_('WriteAction', automation.Action) -ReadAction = ds1307_ns.class_('ReadAction', automation.Action) - - -CONFIG_SCHEMA = time.TIME_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(DS1307Component), -}).extend(i2c.i2c_device_schema(0x68)) - - -@automation.register_action('ds1307.write_time', WriteAction, cv.Schema({ - cv.GenerateID(): cv.use_id(DS1307Component), -})) +CODEOWNERS = ["@badbadc0ffee"] +DEPENDENCIES = ["i2c"] +ds1307_ns = cg.esphome_ns.namespace("ds1307") +DS1307Component = ds1307_ns.class_("DS1307Component", time.RealTimeClock, i2c.I2CDevice) +WriteAction = ds1307_ns.class_("WriteAction", automation.Action) +ReadAction = ds1307_ns.class_("ReadAction", automation.Action) + + +CONFIG_SCHEMA = time.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(DS1307Component), + } +).extend(i2c.i2c_device_schema(0x68)) + + +@automation.register_action( + "ds1307.write_time", + WriteAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(DS1307Component), + } + ), +) def ds1307_write_time_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_action('ds1307.read_time', ReadAction, automation.maybe_simple_id({ - cv.GenerateID(): cv.use_id(DS1307Component), -})) +@automation.register_action( + "ds1307.read_time", + ReadAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(DS1307Component), + } + ), +) def ds1307_read_time_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/duty_cycle/sensor.py b/esphome/components/duty_cycle/sensor.py index 51d99aae6acb..0dd91bade373 100644 --- a/esphome/components/duty_cycle/sensor.py +++ b/esphome/components/duty_cycle/sensor.py @@ -2,16 +2,31 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor -from esphome.const import CONF_ID, CONF_PIN, UNIT_PERCENT, ICON_PERCENT +from esphome.const import ( + CONF_ID, + CONF_PIN, + DEVICE_CLASS_EMPTY, + UNIT_PERCENT, + ICON_PERCENT, +) -duty_cycle_ns = cg.esphome_ns.namespace('duty_cycle') -DutyCycleSensor = duty_cycle_ns.class_('DutyCycleSensor', sensor.Sensor, cg.PollingComponent) +duty_cycle_ns = cg.esphome_ns.namespace("duty_cycle") +DutyCycleSensor = duty_cycle_ns.class_( + "DutyCycleSensor", sensor.Sensor, cg.PollingComponent +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_PERCENT, 1).extend({ - cv.GenerateID(): cv.declare_id(DutyCycleSensor), - cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema, - pins.validate_has_interrupt), -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_PERCENT, ICON_PERCENT, 1, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(DutyCycleSensor), + cv.Required(CONF_PIN): cv.All( + pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt + ), + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): diff --git a/esphome/components/e131/__init__.py b/esphome/components/e131/__init__.py index 0ba16bc9285e..b33aef8cf3c2 100644 --- a/esphome/components/e131/__init__.py +++ b/esphome/components/e131/__init__.py @@ -4,28 +4,29 @@ from esphome.components.light.effects import register_addressable_effect from esphome.const import CONF_ID, CONF_NAME, CONF_METHOD, CONF_CHANNELS -e131_ns = cg.esphome_ns.namespace('e131') -E131AddressableLightEffect = e131_ns.class_('E131AddressableLightEffect', AddressableLightEffect) -E131Component = e131_ns.class_('E131Component', cg.Component) +e131_ns = cg.esphome_ns.namespace("e131") +E131AddressableLightEffect = e131_ns.class_( + "E131AddressableLightEffect", AddressableLightEffect +) +E131Component = e131_ns.class_("E131Component", cg.Component) -METHODS = { - 'UNICAST': e131_ns.E131_UNICAST, - 'MULTICAST': e131_ns.E131_MULTICAST -} +METHODS = {"UNICAST": e131_ns.E131_UNICAST, "MULTICAST": e131_ns.E131_MULTICAST} CHANNELS = { - 'MONO': e131_ns.E131_MONO, - 'RGB': e131_ns.E131_RGB, - 'RGBW': e131_ns.E131_RGBW + "MONO": e131_ns.E131_MONO, + "RGB": e131_ns.E131_RGB, + "RGBW": e131_ns.E131_RGBW, } -CONF_UNIVERSE = 'universe' -CONF_E131_ID = 'e131_id' +CONF_UNIVERSE = "universe" +CONF_E131_ID = "e131_id" -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(E131Component), - cv.Optional(CONF_METHOD, default='MULTICAST'): cv.one_of(*METHODS, upper=True), -}) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(E131Component), + cv.Optional(CONF_METHOD, default="MULTICAST"): cv.one_of(*METHODS, upper=True), + } +) def to_code(config): @@ -34,11 +35,16 @@ def to_code(config): cg.add(var.set_method(METHODS[config[CONF_METHOD]])) -@register_addressable_effect('e131', E131AddressableLightEffect, "E1.31", { - cv.GenerateID(CONF_E131_ID): cv.use_id(E131Component), - cv.Required(CONF_UNIVERSE): cv.int_range(min=1, max=512), - cv.Optional(CONF_CHANNELS, default='RGB'): cv.one_of(*CHANNELS, upper=True) -}) +@register_addressable_effect( + "e131", + E131AddressableLightEffect, + "E1.31", + { + cv.GenerateID(CONF_E131_ID): cv.use_id(E131Component), + cv.Required(CONF_UNIVERSE): cv.int_range(min=1, max=512), + cv.Optional(CONF_CHANNELS, default="RGB"): cv.one_of(*CHANNELS, upper=True), + }, +) def e131_light_effect_to_code(config, effect_id): parent = yield cg.get_variable(config[CONF_E131_ID]) diff --git a/esphome/components/e131/e131_addressable_light_effect.cpp b/esphome/components/e131/e131_addressable_light_effect.cpp index 8657d828c514..5bf28f0f1174 100644 --- a/esphome/components/e131/e131_addressable_light_effect.cpp +++ b/esphome/components/e131/e131_addressable_light_effect.cpp @@ -40,7 +40,7 @@ void E131AddressableLightEffect::stop() { AddressableLightEffect::stop(); } -void E131AddressableLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) { +void E131AddressableLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { // ignore, it is run by `E131Component::update()` } @@ -53,7 +53,8 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet int output_offset = (universe - first_universe_) * get_lights_per_universe(); // limit amount of lights per universe and received - int output_end = std::min(it->size(), std::min(output_offset + get_lights_per_universe(), packet.count - 1)); + int output_end = + std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1)); auto input_data = packet.values + 1; ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %d-%d.", get_name().c_str(), universe, output_offset, @@ -63,22 +64,22 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet case E131_MONO: for (; output_offset < output_end; output_offset++, input_data++) { auto output = (*it)[output_offset]; - output.set(light::ESPColor(input_data[0], input_data[0], input_data[0], input_data[0])); + output.set(Color(input_data[0], input_data[0], input_data[0], input_data[0])); } break; case E131_RGB: for (; output_offset < output_end; output_offset++, input_data += 3) { auto output = (*it)[output_offset]; - output.set(light::ESPColor(input_data[0], input_data[1], input_data[2], - (input_data[0] + input_data[1] + input_data[2]) / 3)); + output.set( + Color(input_data[0], input_data[1], input_data[2], (input_data[0] + input_data[1] + input_data[2]) / 3)); } break; case E131_RGBW: for (; output_offset < output_end; output_offset++, input_data += 4) { auto output = (*it)[output_offset]; - output.set(light::ESPColor(input_data[0], input_data[1], input_data[2], input_data[3])); + output.set(Color(input_data[0], input_data[1], input_data[2], input_data[3])); } break; } diff --git a/esphome/components/e131/e131_addressable_light_effect.h b/esphome/components/e131/e131_addressable_light_effect.h index 85af4fe7a924..1ab5d431643d 100644 --- a/esphome/components/e131/e131_addressable_light_effect.h +++ b/esphome/components/e131/e131_addressable_light_effect.h @@ -18,7 +18,7 @@ class E131AddressableLightEffect : public light::AddressableLightEffect { public: void start() override; void stop() override; - void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override; + void apply(light::AddressableLight &it, const Color ¤t_color) override; public: int get_data_per_universe() const; diff --git a/esphome/components/endstop/cover.py b/esphome/components/endstop/cover.py index 0d65cc10785f..b463d5d96006 100644 --- a/esphome/components/endstop/cover.py +++ b/esphome/components/endstop/cover.py @@ -2,27 +2,34 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import binary_sensor, cover -from esphome.const import CONF_CLOSE_ACTION, CONF_CLOSE_DURATION, \ - CONF_CLOSE_ENDSTOP, CONF_ID, CONF_OPEN_ACTION, CONF_OPEN_DURATION, \ - CONF_OPEN_ENDSTOP, CONF_STOP_ACTION, CONF_MAX_DURATION - -endstop_ns = cg.esphome_ns.namespace('endstop') -EndstopCover = endstop_ns.class_('EndstopCover', cover.Cover, cg.Component) - -CONFIG_SCHEMA = cover.COVER_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(EndstopCover), - cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True), - - cv.Required(CONF_OPEN_ENDSTOP): cv.use_id(binary_sensor.BinarySensor), - cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True), - cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds, - - cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True), - cv.Required(CONF_CLOSE_ENDSTOP): cv.use_id(binary_sensor.BinarySensor), - cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds, - - cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds, -}).extend(cv.COMPONENT_SCHEMA) +from esphome.const import ( + CONF_CLOSE_ACTION, + CONF_CLOSE_DURATION, + CONF_CLOSE_ENDSTOP, + CONF_ID, + CONF_OPEN_ACTION, + CONF_OPEN_DURATION, + CONF_OPEN_ENDSTOP, + CONF_STOP_ACTION, + CONF_MAX_DURATION, +) + +endstop_ns = cg.esphome_ns.namespace("endstop") +EndstopCover = endstop_ns.class_("EndstopCover", cover.Cover, cg.Component) + +CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(EndstopCover), + cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_OPEN_ENDSTOP): cv.use_id(binary_sensor.BinarySensor), + cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds, + cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_CLOSE_ENDSTOP): cv.use_id(binary_sensor.BinarySensor), + cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds, + cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -30,17 +37,23 @@ def to_code(config): yield cg.register_component(var, config) yield cover.register_cover(var, config) - yield automation.build_automation(var.get_stop_trigger(), [], config[CONF_STOP_ACTION]) + yield automation.build_automation( + var.get_stop_trigger(), [], config[CONF_STOP_ACTION] + ) bin = yield cg.get_variable(config[CONF_OPEN_ENDSTOP]) cg.add(var.set_open_endstop(bin)) cg.add(var.set_open_duration(config[CONF_OPEN_DURATION])) - yield automation.build_automation(var.get_open_trigger(), [], config[CONF_OPEN_ACTION]) + yield automation.build_automation( + var.get_open_trigger(), [], config[CONF_OPEN_ACTION] + ) bin = yield cg.get_variable(config[CONF_CLOSE_ENDSTOP]) cg.add(var.set_close_endstop(bin)) cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION])) - yield automation.build_automation(var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]) + yield automation.build_automation( + var.get_close_trigger(), [], config[CONF_CLOSE_ACTION] + ) if CONF_MAX_DURATION in config: cg.add(var.set_max_duration(config[CONF_MAX_DURATION])) diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index 2f02e71fef45..46f56788569c 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -3,26 +3,30 @@ from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, ESP_PLATFORM_ESP32 ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -CONFLICTS_WITH = ['esp32_ble_tracker'] +CONFLICTS_WITH = ["esp32_ble_tracker"] -esp32_ble_beacon_ns = cg.esphome_ns.namespace('esp32_ble_beacon') -ESP32BLEBeacon = esp32_ble_beacon_ns.class_('ESP32BLEBeacon', cg.Component) +esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon") +ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component) -CONF_MAJOR = 'major' -CONF_MINOR = 'minor' +CONF_MAJOR = "major" +CONF_MINOR = "minor" -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(ESP32BLEBeacon), - cv.Required(CONF_TYPE): cv.one_of('IBEACON', upper=True), - cv.Required(CONF_UUID): cv.uuid, - cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t, - cv.Optional(CONF_MINOR, default=61958): cv.uint16_t, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(ESP32BLEBeacon), + cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True), + cv.Required(CONF_UUID): cv.uuid, + cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t, + cv.Optional(CONF_MINOR, default=61958): cv.uint16_t, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): uuid = config[CONF_UUID].hex - uuid_arr = [cg.RawExpression('0x{}'.format(uuid[i:i + 2])) for i in range(0, len(uuid), 2)] + uuid_arr = [ + cg.RawExpression("0x{}".format(uuid[i : i + 2])) for i in range(0, len(uuid), 2) + ] var = cg.new_Pvariable(config[CONF_ID], uuid_arr) yield cg.register_component(var, config) cg.add(var.set_major(config[CONF_MAJOR])) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index c4cc7260fd4c..2e567b49bc07 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -2,33 +2,46 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ID, ESP_PLATFORM_ESP32, CONF_INTERVAL, \ - CONF_DURATION, CONF_TRIGGER_ID, CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_MANUFACTURER_ID, \ - CONF_ON_BLE_ADVERTISE, CONF_ON_BLE_SERVICE_DATA_ADVERTISE, \ - CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE +from esphome.const import ( + CONF_ID, + ESP_PLATFORM_ESP32, + CONF_INTERVAL, + CONF_DURATION, + CONF_TRIGGER_ID, + CONF_MAC_ADDRESS, + CONF_SERVICE_UUID, + CONF_MANUFACTURER_ID, + CONF_ON_BLE_ADVERTISE, + CONF_ON_BLE_SERVICE_DATA_ADVERTISE, + CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, +) from esphome.core import coroutine ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -AUTO_LOAD = ['xiaomi_ble', 'ruuvi_ble'] - -CONF_ESP32_BLE_ID = 'esp32_ble_id' -CONF_SCAN_PARAMETERS = 'scan_parameters' -CONF_WINDOW = 'window' -CONF_ACTIVE = 'active' -esp32_ble_tracker_ns = cg.esphome_ns.namespace('esp32_ble_tracker') -ESP32BLETracker = esp32_ble_tracker_ns.class_('ESP32BLETracker', cg.Component) -ESPBTDeviceListener = esp32_ble_tracker_ns.class_('ESPBTDeviceListener') -ESPBTDevice = esp32_ble_tracker_ns.class_('ESPBTDevice') -ESPBTDeviceConstRef = ESPBTDevice.operator('ref').operator('const') +AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"] + +CONF_ESP32_BLE_ID = "esp32_ble_id" +CONF_SCAN_PARAMETERS = "scan_parameters" +CONF_WINDOW = "window" +CONF_ACTIVE = "active" +esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") +ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component) +ESPBTDeviceListener = esp32_ble_tracker_ns.class_("ESPBTDeviceListener") +ESPBTDevice = esp32_ble_tracker_ns.class_("ESPBTDevice") +ESPBTDeviceConstRef = ESPBTDevice.operator("ref").operator("const") adv_data_t = cg.std_vector.template(cg.uint8) -adv_data_t_const_ref = adv_data_t.operator('ref').operator('const') +adv_data_t_const_ref = adv_data_t.operator("ref").operator("const") # Triggers ESPBTAdvertiseTrigger = esp32_ble_tracker_ns.class_( - 'ESPBTAdvertiseTrigger', automation.Trigger.template(ESPBTDeviceConstRef)) + "ESPBTAdvertiseTrigger", automation.Trigger.template(ESPBTDeviceConstRef) +) BLEServiceDataAdvertiseTrigger = esp32_ble_tracker_ns.class_( - 'BLEServiceDataAdvertiseTrigger', automation.Trigger.template(adv_data_t_const_ref)) + "BLEServiceDataAdvertiseTrigger", automation.Trigger.template(adv_data_t_const_ref) +) BLEManufacturerDataAdvertiseTrigger = esp32_ble_tracker_ns.class_( - 'BLEManufacturerDataAdvertiseTrigger', automation.Trigger.template(adv_data_t_const_ref)) + "BLEManufacturerDataAdvertiseTrigger", + automation.Trigger.template(adv_data_t_const_ref), +) def validate_scan_parameters(config): @@ -37,19 +50,23 @@ def validate_scan_parameters(config): window = config[CONF_WINDOW] if window > interval: - raise cv.Invalid("Scan window ({}) needs to be smaller than scan interval ({})" - "".format(window, interval)) + raise cv.Invalid( + "Scan window ({}) needs to be smaller than scan interval ({})" + "".format(window, interval) + ) if interval.total_milliseconds * 3 > duration.total_milliseconds: - raise cv.Invalid("Scan duration needs to be at least three times the scan interval to" - "cover all BLE channels.") + raise cv.Invalid( + "Scan duration needs to be at least three times the scan interval to" + "cover all BLE channels." + ) return config -bt_uuid16_format = 'XXXX' -bt_uuid32_format = 'XXXXXXXX' -bt_uuid128_format = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' +bt_uuid16_format = "XXXX" +bt_uuid32_format = "XXXXXXXX" +bt_uuid128_format = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" def bt_uuid(value): @@ -60,67 +77,103 @@ def bt_uuid(value): pattern = re.compile("^[A-F|0-9]{4,}$") if not pattern.match(value): raise cv.Invalid( - f"Invalid hexadecimal value for 16 bit UUID format: '{in_value}'") + f"Invalid hexadecimal value for 16 bit UUID format: '{in_value}'" + ) return value if len(value) == len(bt_uuid32_format): pattern = re.compile("^[A-F|0-9]{8,}$") if not pattern.match(value): raise cv.Invalid( - f"Invalid hexadecimal value for 32 bit UUID format: '{in_value}'") + f"Invalid hexadecimal value for 32 bit UUID format: '{in_value}'" + ) return value if len(value) == len(bt_uuid128_format): pattern = re.compile( - "^[A-F|0-9]{8,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{12,}$") + "^[A-F|0-9]{8,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{12,}$" + ) if not pattern.match(value): raise cv.Invalid( - f"Invalid hexadecimal value for 128 UUID format: '{in_value}'") + f"Invalid hexadecimal value for 128 UUID format: '{in_value}'" + ) return value raise cv.Invalid( "Service UUID must be in 16 bit '{}', 32 bit '{}', or 128 bit '{}' format".format( - bt_uuid16_format, bt_uuid32_format, bt_uuid128_format)) + bt_uuid16_format, bt_uuid32_format, bt_uuid128_format + ) + ) def as_hex(value): - return cg.RawExpression(f'0x{value}ULL') + return cg.RawExpression(f"0x{value}ULL") def as_hex_array(value): value = value.replace("-", "") - cpp_array = [f'0x{part}' for part in [value[i:i+2] for i in range(0, len(value), 2)]] + cpp_array = [ + f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)] + ] return cg.RawExpression( - '(uint8_t*)(const uint8_t[16]){{{}}}'.format(','.join(reversed(cpp_array)))) - - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(ESP32BLETracker), - cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All(cv.Schema({ - cv.Optional(CONF_DURATION, default='5min'): cv.positive_time_period_seconds, - cv.Optional(CONF_INTERVAL, default='320ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_WINDOW, default='30ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_ACTIVE, default=True): cv.boolean, - }), validate_scan_parameters), - cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger), - cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, - }), - cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEServiceDataAdvertiseTrigger), - cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, - cv.Required(CONF_SERVICE_UUID): bt_uuid, - }), - cv.Optional(CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEManufacturerDataAdvertiseTrigger), - cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, - cv.Required(CONF_MANUFACTURER_ID): bt_uuid, - }), - - cv.Optional('scan_interval'): cv.invalid("This option has been removed in 1.14 (Reason: " - "it never had an effect)"), -}).extend(cv.COMPONENT_SCHEMA) - -ESP_BLE_DEVICE_SCHEMA = cv.Schema({ - cv.GenerateID(CONF_ESP32_BLE_ID): cv.use_id(ESP32BLETracker), -}) + "(uint8_t*)(const uint8_t[16]){{{}}}".format(",".join(reversed(cpp_array))) + ) + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(ESP32BLETracker), + cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All( + cv.Schema( + { + cv.Optional( + CONF_DURATION, default="5min" + ): cv.positive_time_period_seconds, + cv.Optional( + CONF_INTERVAL, default="320ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_WINDOW, default="30ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_ACTIVE, default=True): cv.boolean, + } + ), + validate_scan_parameters, + ), + cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger), + cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, + } + ), + cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BLEServiceDataAdvertiseTrigger + ), + cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, + cv.Required(CONF_SERVICE_UUID): bt_uuid, + } + ), + cv.Optional( + CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE + ): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BLEManufacturerDataAdvertiseTrigger + ), + cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, + cv.Required(CONF_MANUFACTURER_ID): bt_uuid, + } + ), + cv.Optional("scan_interval"): cv.invalid( + "This option has been removed in 1.14 (Reason: " "it never had an effect)" + ), + } +).extend(cv.COMPONENT_SCHEMA) + +ESP_BLE_DEVICE_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ESP32_BLE_ID): cv.use_id(ESP32BLETracker), + } +) def to_code(config): @@ -135,7 +188,7 @@ def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) if CONF_MAC_ADDRESS in conf: cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) - yield automation.build_automation(trigger, [(ESPBTDeviceConstRef, 'x')], conf) + yield automation.build_automation(trigger, [(ESPBTDeviceConstRef, "x")], conf) for conf in config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) if len(conf[CONF_SERVICE_UUID]) == len(bt_uuid16_format): @@ -147,7 +200,7 @@ def to_code(config): cg.add(trigger.set_service_uuid128(uuid128)) if CONF_MAC_ADDRESS in conf: cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) - yield automation.build_automation(trigger, [(adv_data_t_const_ref, 'x')], conf) + yield automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf) for conf in config.get(CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) if len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid16_format): @@ -159,7 +212,7 @@ def to_code(config): cg.add(trigger.set_manufacturer_uuid128(uuid128)) if CONF_MAC_ADDRESS in conf: cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) - yield automation.build_automation(trigger, [(adv_data_t_const_ref, 'x')], conf) + yield automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf) @coroutine diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 57a6394c8a84..f7169257a4ca 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -1,105 +1,126 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NAME, CONF_PIN, CONF_SCL, CONF_SDA, \ - ESP_PLATFORM_ESP32, CONF_DATA_PINS, CONF_RESET_PIN, CONF_RESOLUTION, CONF_BRIGHTNESS, \ - CONF_CONTRAST +from esphome.const import ( + CONF_FREQUENCY, + CONF_ID, + CONF_NAME, + CONF_PIN, + CONF_SCL, + CONF_SDA, + ESP_PLATFORM_ESP32, + CONF_DATA_PINS, + CONF_RESET_PIN, + CONF_RESOLUTION, + CONF_BRIGHTNESS, + CONF_CONTRAST, +) ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -DEPENDENCIES = ['api'] +DEPENDENCIES = ["api"] -esp32_camera_ns = cg.esphome_ns.namespace('esp32_camera') -ESP32Camera = esp32_camera_ns.class_('ESP32Camera', cg.PollingComponent, cg.Nameable) -ESP32CameraFrameSize = esp32_camera_ns.enum('ESP32CameraFrameSize') +esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") +ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.Nameable) +ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize") FRAME_SIZES = { - '160X120': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, - 'QQVGA': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, - '128X160': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_128X160, - 'QQVGA2': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_128X160, - '176X144': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_176X144, - 'QCIF': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_176X144, - '240X176': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_240X176, - 'HQVGA': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_240X176, - '320X240': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_320X240, - 'QVGA': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_320X240, - '400X296': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_400X296, - 'CIF': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_400X296, - '640X480': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_640X480, - 'VGA': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_640X480, - '800X600': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_800X600, - 'SVGA': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_800X600, - '1024X768': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1024X768, - 'XGA': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1024X768, - '1280X1024': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1280X1024, - 'SXGA': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1280X1024, - '1600X1200': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, - 'UXGA': ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, + "160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, + "QQVGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, + "128X160": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_128X160, + "QQVGA2": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_128X160, + "176X144": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_176X144, + "QCIF": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_176X144, + "240X176": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_240X176, + "HQVGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_240X176, + "320X240": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_320X240, + "QVGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_320X240, + "400X296": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_400X296, + "CIF": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_400X296, + "640X480": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_640X480, + "VGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_640X480, + "800X600": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_800X600, + "SVGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_800X600, + "1024X768": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1024X768, + "XGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1024X768, + "1280X1024": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1280X1024, + "SXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1280X1024, + "1600X1200": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, + "UXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, } -CONF_VSYNC_PIN = 'vsync_pin' -CONF_HREF_PIN = 'href_pin' -CONF_PIXEL_CLOCK_PIN = 'pixel_clock_pin' -CONF_EXTERNAL_CLOCK = 'external_clock' -CONF_I2C_PINS = 'i2c_pins' -CONF_POWER_DOWN_PIN = 'power_down_pin' +CONF_VSYNC_PIN = "vsync_pin" +CONF_HREF_PIN = "href_pin" +CONF_PIXEL_CLOCK_PIN = "pixel_clock_pin" +CONF_EXTERNAL_CLOCK = "external_clock" +CONF_I2C_PINS = "i2c_pins" +CONF_POWER_DOWN_PIN = "power_down_pin" -CONF_MAX_FRAMERATE = 'max_framerate' -CONF_IDLE_FRAMERATE = 'idle_framerate' -CONF_JPEG_QUALITY = 'jpeg_quality' -CONF_VERTICAL_FLIP = 'vertical_flip' -CONF_HORIZONTAL_MIRROR = 'horizontal_mirror' -CONF_SATURATION = 'saturation' -CONF_TEST_PATTERN = 'test_pattern' +CONF_MAX_FRAMERATE = "max_framerate" +CONF_IDLE_FRAMERATE = "idle_framerate" +CONF_JPEG_QUALITY = "jpeg_quality" +CONF_VERTICAL_FLIP = "vertical_flip" +CONF_HORIZONTAL_MIRROR = "horizontal_mirror" +CONF_SATURATION = "saturation" +CONF_TEST_PATTERN = "test_pattern" camera_range_param = cv.int_range(min=-2, max=2) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(ESP32Camera), - cv.Required(CONF_NAME): cv.string, - cv.Required(CONF_DATA_PINS): cv.All([pins.input_pin], cv.Length(min=8, max=8)), - cv.Required(CONF_VSYNC_PIN): pins.input_pin, - cv.Required(CONF_HREF_PIN): pins.input_pin, - cv.Required(CONF_PIXEL_CLOCK_PIN): pins.input_pin, - cv.Required(CONF_EXTERNAL_CLOCK): cv.Schema({ - cv.Required(CONF_PIN): pins.output_pin, - cv.Optional(CONF_FREQUENCY, default='20MHz'): cv.All(cv.frequency, cv.one_of(20e6, 10e6)), - }), - cv.Required(CONF_I2C_PINS): cv.Schema({ - cv.Required(CONF_SDA): pins.output_pin, - cv.Required(CONF_SCL): pins.output_pin, - }), - cv.Optional(CONF_RESET_PIN): pins.output_pin, - cv.Optional(CONF_POWER_DOWN_PIN): pins.output_pin, - - cv.Optional(CONF_MAX_FRAMERATE, default='10 fps'): cv.All(cv.framerate, - cv.Range(min=0, min_included=False, - max=60)), - 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.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, - cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean, - cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean, - cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(ESP32Camera), + cv.Required(CONF_NAME): cv.string, + cv.Required(CONF_DATA_PINS): cv.All([pins.input_pin], cv.Length(min=8, max=8)), + cv.Required(CONF_VSYNC_PIN): pins.input_pin, + cv.Required(CONF_HREF_PIN): pins.input_pin, + cv.Required(CONF_PIXEL_CLOCK_PIN): pins.input_pin, + cv.Required(CONF_EXTERNAL_CLOCK): cv.Schema( + { + cv.Required(CONF_PIN): pins.output_pin, + cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( + cv.frequency, cv.one_of(20e6, 10e6) + ), + } + ), + cv.Required(CONF_I2C_PINS): cv.Schema( + { + cv.Required(CONF_SDA): pins.output_pin, + cv.Required(CONF_SCL): pins.output_pin, + } + ), + cv.Optional(CONF_RESET_PIN): pins.output_pin, + cv.Optional(CONF_POWER_DOWN_PIN): pins.output_pin, + cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( + cv.framerate, cv.Range(min=0, min_included=False, max=60) + ), + 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.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, + cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean, + cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean, + cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, + } +).extend(cv.COMPONENT_SCHEMA) SETTERS = { - CONF_DATA_PINS: 'set_data_pins', - CONF_VSYNC_PIN: 'set_vsync_pin', - CONF_HREF_PIN: 'set_href_pin', - CONF_PIXEL_CLOCK_PIN: 'set_pixel_clock_pin', - CONF_RESET_PIN: 'set_reset_pin', - CONF_POWER_DOWN_PIN: 'set_power_down_pin', - CONF_JPEG_QUALITY: 'set_jpeg_quality', - CONF_VERTICAL_FLIP: 'set_vertical_flip', - CONF_HORIZONTAL_MIRROR: 'set_horizontal_mirror', - CONF_CONTRAST: 'set_contrast', - CONF_BRIGHTNESS: 'set_brightness', - CONF_SATURATION: 'set_saturation', - CONF_TEST_PATTERN: 'set_test_pattern', + CONF_DATA_PINS: "set_data_pins", + CONF_VSYNC_PIN: "set_vsync_pin", + CONF_HREF_PIN: "set_href_pin", + CONF_PIXEL_CLOCK_PIN: "set_pixel_clock_pin", + CONF_RESET_PIN: "set_reset_pin", + CONF_POWER_DOWN_PIN: "set_power_down_pin", + CONF_JPEG_QUALITY: "set_jpeg_quality", + CONF_VERTICAL_FLIP: "set_vertical_flip", + CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror", + CONF_CONTRAST: "set_contrast", + CONF_BRIGHTNESS: "set_brightness", + CONF_SATURATION: "set_saturation", + CONF_TEST_PATTERN: "set_test_pattern", } @@ -122,5 +143,5 @@ def to_code(config): cg.add(var.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE])) cg.add(var.set_frame_size(config[CONF_RESOLUTION])) - cg.add_define('USE_ESP32_CAMERA') - cg.add_build_flag('-DBOARD_HAS_PSRAM') + cg.add_define("USE_ESP32_CAMERA") + cg.add_build_flag("-DBOARD_HAS_PSRAM") diff --git a/esphome/components/esp32_dac/output.py b/esphome/components/esp32_dac/output.py index bb0a8b60e931..8cfc7570e99f 100644 --- a/esphome/components/esp32_dac/output.py +++ b/esphome/components/esp32_dac/output.py @@ -13,13 +13,17 @@ def valid_dac_pin(value): return value -esp32_dac_ns = cg.esphome_ns.namespace('esp32_dac') -ESP32DAC = esp32_dac_ns.class_('ESP32DAC', output.FloatOutput, cg.Component) - -CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ - cv.Required(CONF_ID): cv.declare_id(ESP32DAC), - cv.Required(CONF_PIN): cv.All(pins.internal_gpio_output_pin_schema, valid_dac_pin), -}).extend(cv.COMPONENT_SCHEMA) +esp32_dac_ns = cg.esphome_ns.namespace("esp32_dac") +ESP32DAC = esp32_dac_ns.class_("ESP32DAC", output.FloatOutput, cg.Component) + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(ESP32DAC), + cv.Required(CONF_PIN): cv.All( + pins.internal_gpio_output_pin_schema, valid_dac_pin + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/esp32_hall/sensor.py b/esphome/components/esp32_hall/sensor.py index ec24f1aab69e..363b3f754848 100644 --- a/esphome/components/esp32_hall/sensor.py +++ b/esphome/components/esp32_hall/sensor.py @@ -1,16 +1,30 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import CONF_ID, ESP_PLATFORM_ESP32, UNIT_MICROTESLA, ICON_MAGNET +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_EMPTY, + ESP_PLATFORM_ESP32, + UNIT_MICROTESLA, + ICON_MAGNET, +) ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -esp32_hall_ns = cg.esphome_ns.namespace('esp32_hall') -ESP32HallSensor = esp32_hall_ns.class_('ESP32HallSensor', sensor.Sensor, cg.PollingComponent) +esp32_hall_ns = cg.esphome_ns.namespace("esp32_hall") +ESP32HallSensor = esp32_hall_ns.class_( + "ESP32HallSensor", sensor.Sensor, cg.PollingComponent +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_MICROTESLA, ICON_MAGNET, 1).extend({ - cv.GenerateID(): cv.declare_id(ESP32HallSensor), -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_MICROTESLA, ICON_MAGNET, 1, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(ESP32HallSensor), + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): diff --git a/esphome/components/esp32_touch/__init__.py b/esphome/components/esp32_touch/__init__.py index 3bdc988fcc8d..5c3b47af7762 100644 --- a/esphome/components/esp32_touch/__init__.py +++ b/esphome/components/esp32_touch/__init__.py @@ -1,15 +1,23 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_HIGH_VOLTAGE_REFERENCE, CONF_ID, CONF_IIR_FILTER, \ - CONF_LOW_VOLTAGE_REFERENCE, CONF_MEASUREMENT_DURATION, CONF_SETUP_MODE, CONF_SLEEP_DURATION, \ - CONF_VOLTAGE_ATTENUATION, ESP_PLATFORM_ESP32 +from esphome.const import ( + CONF_HIGH_VOLTAGE_REFERENCE, + CONF_ID, + CONF_IIR_FILTER, + CONF_LOW_VOLTAGE_REFERENCE, + CONF_MEASUREMENT_DURATION, + CONF_SETUP_MODE, + CONF_SLEEP_DURATION, + CONF_VOLTAGE_ATTENUATION, + ESP_PLATFORM_ESP32, +) from esphome.core import TimePeriod -AUTO_LOAD = ['binary_sensor'] +AUTO_LOAD = ["binary_sensor"] ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -esp32_touch_ns = cg.esphome_ns.namespace('esp32_touch') -ESP32TouchComponent = esp32_touch_ns.class_('ESP32TouchComponent', cg.Component) +esp32_touch_ns = cg.esphome_ns.namespace("esp32_touch") +ESP32TouchComponent = esp32_touch_ns.class_("ESP32TouchComponent", cg.Component) def validate_voltage(values): @@ -17,46 +25,56 @@ def validator(value): if isinstance(value, float) and value.is_integer(): value = int(value) value = cv.string(value) - if not value.endswith('V'): - value += 'V' + if not value.endswith("V"): + value += "V" return cv.one_of(*values)(value) return validator LOW_VOLTAGE_REFERENCE = { - '0.5V': cg.global_ns.TOUCH_LVOLT_0V5, - '0.6V': cg.global_ns.TOUCH_LVOLT_0V6, - '0.7V': cg.global_ns.TOUCH_LVOLT_0V7, - '0.8V': cg.global_ns.TOUCH_LVOLT_0V8, + "0.5V": cg.global_ns.TOUCH_LVOLT_0V5, + "0.6V": cg.global_ns.TOUCH_LVOLT_0V6, + "0.7V": cg.global_ns.TOUCH_LVOLT_0V7, + "0.8V": cg.global_ns.TOUCH_LVOLT_0V8, } HIGH_VOLTAGE_REFERENCE = { - '2.4V': cg.global_ns.TOUCH_HVOLT_2V4, - '2.5V': cg.global_ns.TOUCH_HVOLT_2V5, - '2.6V': cg.global_ns.TOUCH_HVOLT_2V6, - '2.7V': cg.global_ns.TOUCH_HVOLT_2V7, + "2.4V": cg.global_ns.TOUCH_HVOLT_2V4, + "2.5V": cg.global_ns.TOUCH_HVOLT_2V5, + "2.6V": cg.global_ns.TOUCH_HVOLT_2V6, + "2.7V": cg.global_ns.TOUCH_HVOLT_2V7, } VOLTAGE_ATTENUATION = { - '1.5V': cg.global_ns.TOUCH_HVOLT_ATTEN_1V5, - '1V': cg.global_ns.TOUCH_HVOLT_ATTEN_1V, - '0.5V': cg.global_ns.TOUCH_HVOLT_ATTEN_0V5, - '0V': cg.global_ns.TOUCH_HVOLT_ATTEN_0V, + "1.5V": cg.global_ns.TOUCH_HVOLT_ATTEN_1V5, + "1V": cg.global_ns.TOUCH_HVOLT_ATTEN_1V, + "0.5V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V5, + "0V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V, } -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(ESP32TouchComponent), - cv.Optional(CONF_SETUP_MODE, default=False): cv.boolean, - cv.Optional(CONF_IIR_FILTER, default='0ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_SLEEP_DURATION, default='27306us'): - cv.All(cv.positive_time_period, cv.Range(max=TimePeriod(microseconds=436906))), - cv.Optional(CONF_MEASUREMENT_DURATION, default='8192us'): - cv.All(cv.positive_time_period, cv.Range(max=TimePeriod(microseconds=8192))), - cv.Optional(CONF_LOW_VOLTAGE_REFERENCE, default='0.5V'): - validate_voltage(LOW_VOLTAGE_REFERENCE), - cv.Optional(CONF_HIGH_VOLTAGE_REFERENCE, default='2.7V'): - validate_voltage(HIGH_VOLTAGE_REFERENCE), - cv.Optional(CONF_VOLTAGE_ATTENUATION, default='0V'): validate_voltage(VOLTAGE_ATTENUATION), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(ESP32TouchComponent), + cv.Optional(CONF_SETUP_MODE, default=False): cv.boolean, + cv.Optional( + CONF_IIR_FILTER, default="0ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_SLEEP_DURATION, default="27306us"): cv.All( + cv.positive_time_period, cv.Range(max=TimePeriod(microseconds=436906)) + ), + cv.Optional(CONF_MEASUREMENT_DURATION, default="8192us"): cv.All( + cv.positive_time_period, cv.Range(max=TimePeriod(microseconds=8192)) + ), + cv.Optional(CONF_LOW_VOLTAGE_REFERENCE, default="0.5V"): validate_voltage( + LOW_VOLTAGE_REFERENCE + ), + cv.Optional(CONF_HIGH_VOLTAGE_REFERENCE, default="2.7V"): validate_voltage( + HIGH_VOLTAGE_REFERENCE + ), + cv.Optional(CONF_VOLTAGE_ATTENUATION, default="0V"): validate_voltage( + VOLTAGE_ATTENUATION + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -69,12 +87,23 @@ def to_code(config): sleep_duration = int(round(config[CONF_SLEEP_DURATION].total_microseconds * 0.15)) cg.add(touch.set_sleep_duration(sleep_duration)) - measurement_duration = int(round(config[CONF_MEASUREMENT_DURATION].total_microseconds * - 7.99987793)) + measurement_duration = int( + round(config[CONF_MEASUREMENT_DURATION].total_microseconds * 7.99987793) + ) cg.add(touch.set_measurement_duration(measurement_duration)) - cg.add(touch.set_low_voltage_reference( - LOW_VOLTAGE_REFERENCE[config[CONF_LOW_VOLTAGE_REFERENCE]])) - cg.add(touch.set_high_voltage_reference( - HIGH_VOLTAGE_REFERENCE[config[CONF_HIGH_VOLTAGE_REFERENCE]])) - cg.add(touch.set_voltage_attenuation(VOLTAGE_ATTENUATION[config[CONF_VOLTAGE_ATTENUATION]])) + cg.add( + touch.set_low_voltage_reference( + LOW_VOLTAGE_REFERENCE[config[CONF_LOW_VOLTAGE_REFERENCE]] + ) + ) + cg.add( + touch.set_high_voltage_reference( + HIGH_VOLTAGE_REFERENCE[config[CONF_HIGH_VOLTAGE_REFERENCE]] + ) + ) + cg.add( + touch.set_voltage_attenuation( + VOLTAGE_ATTENUATION[config[CONF_VOLTAGE_ATTENUATION]] + ) + ) diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index 5142879a0485..f8bc348132b1 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -1,14 +1,20 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_NAME, CONF_PIN, CONF_THRESHOLD, ESP_PLATFORM_ESP32, CONF_ID +from esphome.const import ( + CONF_NAME, + CONF_PIN, + CONF_THRESHOLD, + ESP_PLATFORM_ESP32, + CONF_ID, +) from esphome.pins import validate_gpio_pin from . import esp32_touch_ns, ESP32TouchComponent ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -DEPENDENCIES = ['esp32_touch'] +DEPENDENCIES = ["esp32_touch"] -CONF_ESP32_TOUCH_ID = 'esp32_touch_id' +CONF_ESP32_TOUCH_ID = "esp32_touch_id" TOUCH_PADS = { 4: cg.global_ns.TOUCH_PAD_NUM0, @@ -31,19 +37,27 @@ def validate_touch_pad(value): return value -ESP32TouchBinarySensor = esp32_touch_ns.class_('ESP32TouchBinarySensor', binary_sensor.BinarySensor) +ESP32TouchBinarySensor = esp32_touch_ns.class_( + "ESP32TouchBinarySensor", binary_sensor.BinarySensor +) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(ESP32TouchBinarySensor), - cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_id(ESP32TouchComponent), - cv.Required(CONF_PIN): validate_touch_pad, - cv.Required(CONF_THRESHOLD): cv.uint16_t, -}) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ESP32TouchBinarySensor), + cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_id(ESP32TouchComponent), + cv.Required(CONF_PIN): validate_touch_pad, + cv.Required(CONF_THRESHOLD): cv.uint16_t, + } +) def to_code(config): hub = yield cg.get_variable(config[CONF_ESP32_TOUCH_ID]) - var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME], TOUCH_PADS[config[CONF_PIN]], - config[CONF_THRESHOLD]) + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_NAME], + TOUCH_PADS[config[CONF_PIN]], + config[CONF_THRESHOLD], + ) yield binary_sensor.register_binary_sensor(var, config) cg.add(hub.register_touch_pad(var)) diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index e97349052555..ad7da4e00156 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -2,7 +2,13 @@ from esphome.components import output import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NUMBER, CONF_PIN, ESP_PLATFORM_ESP8266 +from esphome.const import ( + CONF_FREQUENCY, + CONF_ID, + CONF_NUMBER, + CONF_PIN, + ESP_PLATFORM_ESP8266, +) ESP_PLATFORMS = [ESP_PLATFORM_ESP8266] @@ -13,16 +19,20 @@ def valid_pwm_pin(value): return value -esp8266_pwm_ns = cg.esphome_ns.namespace('esp8266_pwm') -ESP8266PWM = esp8266_pwm_ns.class_('ESP8266PWM', output.FloatOutput, cg.Component) -SetFrequencyAction = esp8266_pwm_ns.class_('SetFrequencyAction', automation.Action) +esp8266_pwm_ns = cg.esphome_ns.namespace("esp8266_pwm") +ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Component) +SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action) validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) -CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ - cv.Required(CONF_ID): cv.declare_id(ESP8266PWM), - cv.Required(CONF_PIN): cv.All(pins.internal_gpio_output_pin_schema, valid_pwm_pin), - cv.Optional(CONF_FREQUENCY, default='1kHz'): validate_frequency, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(ESP8266PWM), + cv.Required(CONF_PIN): cv.All( + pins.internal_gpio_output_pin_schema, valid_pwm_pin + ), + cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -36,10 +46,16 @@ def to_code(config): cg.add(var.set_frequency(config[CONF_FREQUENCY])) -@automation.register_action('output.esp8266_pwm.set_frequency', SetFrequencyAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(ESP8266PWM), - cv.Required(CONF_FREQUENCY): cv.templatable(validate_frequency), -})) +@automation.register_action( + "output.esp8266_pwm.set_frequency", + SetFrequencyAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(ESP8266PWM), + cv.Required(CONF_FREQUENCY): cv.templatable(validate_frequency), + } + ), +) def esp8266_set_frequency_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index ce7422d05b65..ca00f33359ad 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -1,47 +1,60 @@ from esphome import pins import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_DOMAIN, CONF_ID, CONF_MANUAL_IP, CONF_STATIC_IP, CONF_TYPE, \ - CONF_USE_ADDRESS, ESP_PLATFORM_ESP32, CONF_GATEWAY, CONF_SUBNET, CONF_DNS1, CONF_DNS2 +from esphome.const import ( + CONF_DOMAIN, + CONF_ID, + CONF_MANUAL_IP, + CONF_STATIC_IP, + CONF_TYPE, + CONF_USE_ADDRESS, + ESP_PLATFORM_ESP32, + CONF_GATEWAY, + CONF_SUBNET, + CONF_DNS1, + CONF_DNS2, +) from esphome.core import CORE, coroutine_with_priority -CONFLICTS_WITH = ['wifi'] +CONFLICTS_WITH = ["wifi"] ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -AUTO_LOAD = ['network'] +AUTO_LOAD = ["network"] -ethernet_ns = cg.esphome_ns.namespace('ethernet') -CONF_PHY_ADDR = 'phy_addr' -CONF_MDC_PIN = 'mdc_pin' -CONF_MDIO_PIN = 'mdio_pin' -CONF_CLK_MODE = 'clk_mode' -CONF_POWER_PIN = 'power_pin' +ethernet_ns = cg.esphome_ns.namespace("ethernet") +CONF_PHY_ADDR = "phy_addr" +CONF_MDC_PIN = "mdc_pin" +CONF_MDIO_PIN = "mdio_pin" +CONF_CLK_MODE = "clk_mode" +CONF_POWER_PIN = "power_pin" -EthernetType = ethernet_ns.enum('EthernetType') +EthernetType = ethernet_ns.enum("EthernetType") ETHERNET_TYPES = { - 'LAN8720': EthernetType.ETHERNET_TYPE_LAN8720, - 'TLK110': EthernetType.ETHERNET_TYPE_TLK110, + "LAN8720": EthernetType.ETHERNET_TYPE_LAN8720, + "TLK110": EthernetType.ETHERNET_TYPE_TLK110, } -eth_clock_mode_t = cg.global_ns.enum('eth_clock_mode_t') +eth_clock_mode_t = cg.global_ns.enum("eth_clock_mode_t") CLK_MODES = { - 'GPIO0_IN': eth_clock_mode_t.ETH_CLOCK_GPIO0_IN, - 'GPIO0_OUT': eth_clock_mode_t.ETH_CLOCK_GPIO0_OUT, - 'GPIO16_OUT': eth_clock_mode_t.ETH_CLOCK_GPIO16_OUT, - 'GPIO17_OUT': eth_clock_mode_t.ETH_CLOCK_GPIO17_OUT, + "GPIO0_IN": eth_clock_mode_t.ETH_CLOCK_GPIO0_IN, + "GPIO0_OUT": eth_clock_mode_t.ETH_CLOCK_GPIO0_OUT, + "GPIO16_OUT": eth_clock_mode_t.ETH_CLOCK_GPIO16_OUT, + "GPIO17_OUT": eth_clock_mode_t.ETH_CLOCK_GPIO17_OUT, } -MANUAL_IP_SCHEMA = cv.Schema({ - cv.Required(CONF_STATIC_IP): cv.ipv4, - cv.Required(CONF_GATEWAY): cv.ipv4, - cv.Required(CONF_SUBNET): cv.ipv4, - cv.Optional(CONF_DNS1, default="0.0.0.0"): cv.ipv4, - cv.Optional(CONF_DNS2, default="0.0.0.0"): cv.ipv4, -}) +MANUAL_IP_SCHEMA = cv.Schema( + { + cv.Required(CONF_STATIC_IP): cv.ipv4, + cv.Required(CONF_GATEWAY): cv.ipv4, + cv.Required(CONF_SUBNET): cv.ipv4, + cv.Optional(CONF_DNS1, default="0.0.0.0"): cv.ipv4, + cv.Optional(CONF_DNS2, default="0.0.0.0"): cv.ipv4, + } +) -EthernetComponent = ethernet_ns.class_('EthernetComponent', cg.Component) -IPAddress = cg.global_ns.class_('IPAddress') -ManualIP = ethernet_ns.struct('ManualIP') +EthernetComponent = ethernet_ns.class_("EthernetComponent", cg.Component) +IPAddress = cg.global_ns.class_("IPAddress") +ManualIP = ethernet_ns.struct("ManualIP") def validate(config): @@ -54,30 +67,38 @@ def validate(config): return config -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(EthernetComponent), - cv.Required(CONF_TYPE): cv.enum(ETHERNET_TYPES, upper=True), - cv.Required(CONF_MDC_PIN): pins.output_pin, - cv.Required(CONF_MDIO_PIN): pins.input_output_pin, - cv.Optional(CONF_CLK_MODE, default='GPIO0_IN'): cv.enum(CLK_MODES, upper=True, space='_'), - cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), - cv.Optional(CONF_POWER_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA, - cv.Optional(CONF_DOMAIN, default='.local'): cv.domain_name, - cv.Optional(CONF_USE_ADDRESS): cv.string_strict, - - cv.Optional('hostname'): cv.invalid("The hostname option has been removed in 1.11.0"), -}).extend(cv.COMPONENT_SCHEMA), validate) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(EthernetComponent), + cv.Required(CONF_TYPE): cv.enum(ETHERNET_TYPES, upper=True), + cv.Required(CONF_MDC_PIN): pins.output_pin, + cv.Required(CONF_MDIO_PIN): pins.input_output_pin, + cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum( + CLK_MODES, upper=True, space="_" + ), + cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), + cv.Optional(CONF_POWER_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA, + cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, + cv.Optional(CONF_USE_ADDRESS): cv.string_strict, + cv.Optional("hostname"): cv.invalid( + "The hostname option has been removed in 1.11.0" + ), + } + ).extend(cv.COMPONENT_SCHEMA), + validate, +) def manual_ip(config): return cg.StructInitializer( ManualIP, - ('static_ip', IPAddress(*config[CONF_STATIC_IP].args)), - ('gateway', IPAddress(*config[CONF_GATEWAY].args)), - ('subnet', IPAddress(*config[CONF_SUBNET].args)), - ('dns1', IPAddress(*config[CONF_DNS1].args)), - ('dns2', IPAddress(*config[CONF_DNS2].args)), + ("static_ip", IPAddress(*config[CONF_STATIC_IP].args)), + ("gateway", IPAddress(*config[CONF_GATEWAY].args)), + ("subnet", IPAddress(*config[CONF_SUBNET].args)), + ("dns1", IPAddress(*config[CONF_DNS1].args)), + ("dns2", IPAddress(*config[CONF_DNS2].args)), ) @@ -100,4 +121,4 @@ def to_code(config): if CONF_MANUAL_IP in config: cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) - cg.add_define('USE_ETHERNET') + cg.add_define("USE_ETHERNET") diff --git a/esphome/components/exposure_notifications/__init__.py b/esphome/components/exposure_notifications/__init__.py index 8175a2d3aa00..1bd800778554 100644 --- a/esphome/components/exposure_notifications/__init__.py +++ b/esphome/components/exposure_notifications/__init__.py @@ -4,26 +4,36 @@ from esphome.components import esp32_ble_tracker from esphome.const import CONF_TRIGGER_ID -CODEOWNERS = ['@OttoWinter'] -DEPENDENCIES = ['esp32_ble_tracker'] +CODEOWNERS = ["@OttoWinter"] +DEPENDENCIES = ["esp32_ble_tracker"] -exposure_notifications_ns = cg.esphome_ns.namespace('exposure_notifications') -ExposureNotification = exposure_notifications_ns.struct('ExposureNotification') +exposure_notifications_ns = cg.esphome_ns.namespace("exposure_notifications") +ExposureNotification = exposure_notifications_ns.struct("ExposureNotification") ExposureNotificationTrigger = exposure_notifications_ns.class_( - 'ExposureNotificationTrigger', esp32_ble_tracker.ESPBTDeviceListener, - automation.Trigger.template(ExposureNotification)) + "ExposureNotificationTrigger", + esp32_ble_tracker.ESPBTDeviceListener, + automation.Trigger.template(ExposureNotification), +) -CONF_ON_EXPOSURE_NOTIFICATION = 'on_exposure_notification' +CONF_ON_EXPOSURE_NOTIFICATION = "on_exposure_notification" -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ON_EXPOSURE_NOTIFICATION): automation.validate_automation(cv.Schema({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ExposureNotificationTrigger), - }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)), -}) +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ON_EXPOSURE_NOTIFICATION): automation.validate_automation( + cv.Schema( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ExposureNotificationTrigger + ), + } + ).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + ), + } +) def to_code(config): for conf in config.get(CONF_ON_EXPOSURE_NOTIFICATION, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) - yield automation.build_automation(trigger, [(ExposureNotification, 'x')], conf) + yield automation.build_automation(trigger, [(ExposureNotification, "x")], conf) yield esp32_ble_tracker.register_ble_device(trigger, conf) diff --git a/esphome/components/ezo/sensor.py b/esphome/components/ezo/sensor.py index ee896eea15fe..12640f403873 100644 --- a/esphome/components/ezo/sensor.py +++ b/esphome/components/ezo/sensor.py @@ -3,17 +3,25 @@ from esphome.components import i2c, sensor from esphome.const import CONF_ID -CODEOWNERS = ['@ssieb'] +CODEOWNERS = ["@ssieb"] -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -ezo_ns = cg.esphome_ns.namespace('ezo') +ezo_ns = cg.esphome_ns.namespace("ezo") -EZOSensor = ezo_ns.class_('EZOSensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice) +EZOSensor = ezo_ns.class_( + "EZOSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(EZOSensor), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(None)) +CONFIG_SCHEMA = ( + sensor.SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(EZOSensor), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(None)) +) def to_code(config): diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 7b0a79a0d3a5..10e38682e7f5 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -3,42 +3,49 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import mqtt -from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID, CONF_OSCILLATING, \ - CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, CONF_SPEED, \ - CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_NAME +from esphome.const import ( + CONF_ID, + CONF_INTERNAL, + CONF_MQTT_ID, + CONF_OSCILLATING, + CONF_OSCILLATION_COMMAND_TOPIC, + CONF_OSCILLATION_STATE_TOPIC, + CONF_SPEED, + CONF_SPEED_COMMAND_TOPIC, + CONF_SPEED_STATE_TOPIC, + CONF_NAME, +) from esphome.core import CORE, coroutine, coroutine_with_priority IS_PLATFORM_COMPONENT = True -fan_ns = cg.esphome_ns.namespace('fan') -FanState = fan_ns.class_('FanState', cg.Nameable, cg.Component) -MakeFan = cg.Application.struct('MakeFan') +fan_ns = cg.esphome_ns.namespace("fan") +FanState = fan_ns.class_("FanState", cg.Nameable, cg.Component) +MakeFan = cg.Application.struct("MakeFan") # Actions -TurnOnAction = fan_ns.class_('TurnOnAction', automation.Action) -TurnOffAction = fan_ns.class_('TurnOffAction', automation.Action) -ToggleAction = fan_ns.class_('ToggleAction', automation.Action) - -FanSpeed = fan_ns.enum('FanSpeed') -FAN_SPEEDS = { - 'OFF': FanSpeed.FAN_SPEED_OFF, - 'LOW': FanSpeed.FAN_SPEED_LOW, - 'MEDIUM': FanSpeed.FAN_SPEED_MEDIUM, - 'HIGH': FanSpeed.FAN_SPEED_HIGH, -} - -FAN_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(FanState), - cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTFanComponent), - cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(cv.requires_component('mqtt'), - cv.publish_topic), - cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(cv.requires_component('mqtt'), - cv.subscribe_topic), - cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(cv.requires_component('mqtt'), - cv.publish_topic), - cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(cv.requires_component('mqtt'), - cv.subscribe_topic), -}) +TurnOnAction = fan_ns.class_("TurnOnAction", automation.Action) +TurnOffAction = fan_ns.class_("TurnOffAction", automation.Action) +ToggleAction = fan_ns.class_("ToggleAction", automation.Action) + +FAN_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(FanState), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), + cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + } +) @coroutine @@ -52,14 +59,23 @@ def setup_fan_core_(var, config): yield mqtt.register_mqtt_component(mqtt_, config) if CONF_OSCILLATION_STATE_TOPIC in config: - cg.add(mqtt_.set_custom_oscillation_state_topic(config[CONF_OSCILLATION_STATE_TOPIC])) + cg.add( + mqtt_.set_custom_oscillation_state_topic( + config[CONF_OSCILLATION_STATE_TOPIC] + ) + ) if CONF_OSCILLATION_COMMAND_TOPIC in config: - cg.add(mqtt_.set_custom_oscillation_command_topic( - config[CONF_OSCILLATION_COMMAND_TOPIC])) + cg.add( + mqtt_.set_custom_oscillation_command_topic( + config[CONF_OSCILLATION_COMMAND_TOPIC] + ) + ) if CONF_SPEED_STATE_TOPIC in config: cg.add(mqtt_.set_custom_speed_state_topic(config[CONF_SPEED_STATE_TOPIC])) if CONF_SPEED_COMMAND_TOPIC in config: - cg.add(mqtt_.set_custom_speed_command_topic(config[CONF_SPEED_COMMAND_TOPIC])) + cg.add( + mqtt_.set_custom_speed_command_topic(config[CONF_SPEED_COMMAND_TOPIC]) + ) @coroutine @@ -78,28 +94,36 @@ def create_fan_state(config): yield var -FAN_ACTION_SCHEMA = maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(FanState), -}) +FAN_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(FanState), + } +) -@automation.register_action('fan.toggle', ToggleAction, FAN_ACTION_SCHEMA) +@automation.register_action("fan.toggle", ToggleAction, FAN_ACTION_SCHEMA) def fan_toggle_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_action('fan.turn_off', TurnOffAction, FAN_ACTION_SCHEMA) +@automation.register_action("fan.turn_off", TurnOffAction, FAN_ACTION_SCHEMA) def fan_turn_off_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_action('fan.turn_on', TurnOnAction, maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(FanState), - cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean), - cv.Optional(CONF_SPEED): cv.templatable(cv.enum(FAN_SPEEDS, upper=True)), -})) +@automation.register_action( + "fan.turn_on", + TurnOnAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(FanState), + cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean), + cv.Optional(CONF_SPEED): cv.templatable(cv.int_range(1)), + } + ), +) def fan_turn_on_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -107,12 +131,12 @@ def fan_turn_on_to_code(config, action_id, template_arg, args): template_ = yield cg.templatable(config[CONF_OSCILLATING], args, bool) cg.add(var.set_oscillating(template_)) if CONF_SPEED in config: - template_ = yield cg.templatable(config[CONF_SPEED], args, FanSpeed) + template_ = yield cg.templatable(config[CONF_SPEED], args, int) cg.add(var.set_speed(template_)) yield var @coroutine_with_priority(100.0) def to_code(config): - cg.add_define('USE_FAN') + cg.add_define("USE_FAN") cg.add_global(fan_ns.using) diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index d96ed994e8d3..25c92075baae 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -12,7 +12,7 @@ template class TurnOnAction : public Action { explicit TurnOnAction(FanState *state) : state_(state) {} TEMPLATABLE_VALUE(bool, oscillating) - TEMPLATABLE_VALUE(FanSpeed, speed) + TEMPLATABLE_VALUE(int, speed) void play(Ts... x) override { auto call = this->state_->turn_on(); diff --git a/esphome/components/fan/fan_helpers.cpp b/esphome/components/fan/fan_helpers.cpp new file mode 100644 index 000000000000..be16e6bb646f --- /dev/null +++ b/esphome/components/fan/fan_helpers.cpp @@ -0,0 +1,20 @@ +#include +#include "fan_helpers.h" + +namespace esphome { +namespace fan { + +FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) { + const auto speed_ratio = static_cast(speed_level) / (supported_speed_levels + 1); + const auto legacy_level = static_cast(clamp(ceilf(speed_ratio * 3), 1, 3)); + return static_cast(legacy_level - 1); +} + +int speed_enum_to_level(FanSpeed speed, int supported_speed_levels) { + const auto enum_level = static_cast(speed) + 1; + const auto speed_level = roundf(enum_level / 3.0f * supported_speed_levels); + return static_cast(speed_level); +} + +} // namespace fan +} // namespace esphome diff --git a/esphome/components/fan/fan_helpers.h b/esphome/components/fan/fan_helpers.h new file mode 100644 index 000000000000..138aa5bca30a --- /dev/null +++ b/esphome/components/fan/fan_helpers.h @@ -0,0 +1,11 @@ +#pragma once +#include "fan_state.h" + +namespace esphome { +namespace fan { + +FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels); +int speed_enum_to_level(FanSpeed speed, int supported_speed_levels); + +} // namespace fan +} // namespace esphome diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index ae58b04150a7..5a3a7ecebcd7 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -1,4 +1,5 @@ #include "fan_state.h" +#include "fan_helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -20,7 +21,7 @@ FanStateCall FanState::make_call() { return FanStateCall(this); } struct FanStateRTCState { bool state; - FanSpeed speed; + int speed; bool oscillating; FanDirection direction; }; @@ -52,16 +53,8 @@ void FanStateCall::perform() const { this->state_->direction = *this->direction_; } if (this->speed_.has_value()) { - switch (*this->speed_) { - case FAN_SPEED_LOW: - case FAN_SPEED_MEDIUM: - case FAN_SPEED_HIGH: - this->state_->speed = *this->speed_; - break; - default: - // protect from invalid input - break; - } + const int speed_count = this->state_->get_traits().supported_speed_count(); + this->state_->speed = static_cast(clamp(*this->speed_, 1, speed_count)); } FanStateRTCState saved{}; @@ -73,13 +66,15 @@ void FanStateCall::perform() const { this->state_->state_callback_.call(); } -FanStateCall &FanStateCall::set_speed(const char *speed) { - if (strcasecmp(speed, "low") == 0) { - this->set_speed(FAN_SPEED_LOW); - } else if (strcasecmp(speed, "medium") == 0) { - this->set_speed(FAN_SPEED_MEDIUM); - } else if (strcasecmp(speed, "high") == 0) { - this->set_speed(FAN_SPEED_HIGH); + +FanStateCall &FanStateCall::set_speed(const char *legacy_speed) { + const auto supported_speed_count = this->state_->get_traits().supported_speed_count(); + if (strcasecmp(legacy_speed, "low") == 0) { + this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count)); + } else if (strcasecmp(legacy_speed, "medium") == 0) { + this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count)); + } else if (strcasecmp(legacy_speed, "high") == 0) { + this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count)); } return *this; } diff --git a/esphome/components/fan/fan_state.h b/esphome/components/fan/fan_state.h index 7ab8337e945a..a0dda4083a73 100644 --- a/esphome/components/fan/fan_state.h +++ b/esphome/components/fan/fan_state.h @@ -3,12 +3,13 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" +#include "esphome/core/log.h" #include "fan_traits.h" namespace esphome { namespace fan { -/// Simple enum to represent the speed of a fan. +/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon enum FanSpeed { FAN_SPEED_LOW = 0, ///< The fan is running on low speed. FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed. @@ -40,15 +41,11 @@ class FanStateCall { this->oscillating_ = oscillating; return *this; } - FanStateCall &set_speed(FanSpeed speed) { + FanStateCall &set_speed(int speed) { this->speed_ = speed; return *this; } - FanStateCall &set_speed(optional speed) { - this->speed_ = speed; - return *this; - } - FanStateCall &set_speed(const char *speed); + FanStateCall &set_speed(const char *legacy_speed); FanStateCall &set_direction(FanDirection direction) { this->direction_ = direction; return *this; @@ -63,8 +60,8 @@ class FanStateCall { protected: FanState *const state_; optional binary_state_; - optional oscillating_{}; - optional speed_{}; + optional oscillating_; + optional speed_; optional direction_{}; }; @@ -86,8 +83,8 @@ class FanState : public Nameable, public Component { bool state{false}; /// The current oscillation state of the fan. bool oscillating{false}; - /// The current fan speed. - FanSpeed speed{FAN_SPEED_HIGH}; + /// The current fan speed level + int speed{}; /// The current direction of the fan FanDirection direction{FAN_DIRECTION_FORWARD}; diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index 75663484c58e..e69d8e2e53b6 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -6,8 +6,8 @@ namespace fan { class FanTraits { public: FanTraits() = default; - FanTraits(bool oscillation, bool speed, bool direction) - : oscillation_(oscillation), speed_(speed), direction_(direction) {} + FanTraits(bool oscillation, bool speed, bool direction, int speed_count) + : oscillation_(oscillation), speed_(speed), direction_(direction), speed_count_(speed_count) {} /// Return if this fan supports oscillation. bool supports_oscillation() const { return this->oscillation_; } @@ -15,8 +15,12 @@ class FanTraits { void set_oscillation(bool oscillation) { this->oscillation_ = oscillation; } /// Return if this fan supports speed modes. bool supports_speed() const { return this->speed_; } - /// Set whether this fan supports speed modes. + /// Set whether this fan supports speed levels. void set_speed(bool speed) { this->speed_ = speed; } + /// Return how many speed levels the fan has + int supported_speed_count() const { return this->speed_count_; } + /// Set how many speed levels this fan has. + void set_supported_speed_count(int speed_count) { this->speed_count_ = speed_count; } /// Return if this fan supports changing direction bool supports_direction() const { return this->direction_; } /// Set whether this fan supports changing direction @@ -26,6 +30,7 @@ class FanTraits { bool oscillation_{false}; bool speed_{false}; bool direction_{false}; + int speed_count_{}; }; } // namespace fan diff --git a/esphome/components/fastled_base/__init__.py b/esphome/components/fastled_base/__init__.py index ab78f7537f90..4c720191b998 100644 --- a/esphome/components/fastled_base/__init__.py +++ b/esphome/components/fastled_base/__init__.py @@ -1,29 +1,37 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light -from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MAX_REFRESH_RATE +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_NUM_LEDS, + CONF_RGB_ORDER, + CONF_MAX_REFRESH_RATE, +) from esphome.core import coroutine -CODEOWNERS = ['@OttoWinter'] -fastled_base_ns = cg.esphome_ns.namespace('fastled_base') -FastLEDLightOutput = fastled_base_ns.class_('FastLEDLightOutput', light.AddressableLight) +CODEOWNERS = ["@OttoWinter"] +fastled_base_ns = cg.esphome_ns.namespace("fastled_base") +FastLEDLightOutput = fastled_base_ns.class_( + "FastLEDLightOutput", light.AddressableLight +) RGB_ORDERS = [ - 'RGB', - 'RBG', - 'GRB', - 'GBR', - 'BRG', - 'BGR', + "RGB", + "RBG", + "GRB", + "GBR", + "BRG", + "BGR", ] -BASE_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(FastLEDLightOutput), - - 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, -}).extend(cv.COMPONENT_SCHEMA) +BASE_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(FastLEDLightOutput), + 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, + } +).extend(cv.COMPONENT_SCHEMA) @coroutine @@ -38,5 +46,5 @@ def new_fastled_light(config): # https://github.com/FastLED/FastLED/blob/master/library.json # 3.3.3 has an issue on ESP32 with RMT and fastled_clockless: # https://github.com/esphome/issues/issues/1375 - cg.add_library('FastLED', '3.3.2') + cg.add_library("FastLED", "3.3.2") yield var diff --git a/esphome/components/fastled_clockless/light.py b/esphome/components/fastled_clockless/light.py index 30fa910e598e..b8a36ff39044 100644 --- a/esphome/components/fastled_clockless/light.py +++ b/esphome/components/fastled_clockless/light.py @@ -4,46 +4,51 @@ from esphome.components import fastled_base from esphome.const import CONF_CHIPSET, CONF_NUM_LEDS, CONF_PIN, CONF_RGB_ORDER -AUTO_LOAD = ['fastled_base'] +AUTO_LOAD = ["fastled_base"] CHIPSETS = [ - 'NEOPIXEL', - 'TM1829', - 'TM1809', - 'TM1804', - 'TM1803', - 'UCS1903', - 'UCS1903B', - 'UCS1904', - 'UCS2903', - 'WS2812', - 'WS2852', - 'WS2812B', - 'SK6812', - 'SK6822', - 'APA106', - 'PL9823', - 'WS2811', - 'WS2813', - 'APA104', - 'WS2811_400', - 'GW6205', - 'GW6205_400', - 'LPD1886', - 'LPD1886_8BIT', + "NEOPIXEL", + "TM1829", + "TM1809", + "TM1804", + "TM1803", + "UCS1903", + "UCS1903B", + "UCS1904", + "UCS2903", + "WS2812", + "WS2852", + "WS2812B", + "SK6812", + "SK6822", + "APA106", + "PL9823", + "WS2811", + "WS2813", + "APA104", + "WS2811_400", + "GW6205", + "GW6205_400", + "LPD1886", + "LPD1886_8BIT", ] def validate(value): - if value[CONF_CHIPSET] == 'NEOPIXEL' and CONF_RGB_ORDER in value: + if value[CONF_CHIPSET] == "NEOPIXEL" and CONF_RGB_ORDER in value: raise cv.Invalid("NEOPIXEL doesn't support RGB order") return value -CONFIG_SCHEMA = cv.All(fastled_base.BASE_SCHEMA.extend({ - cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), - cv.Required(CONF_PIN): pins.output_pin, -}), validate) +CONFIG_SCHEMA = cv.All( + fastled_base.BASE_SCHEMA.extend( + { + cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), + cv.Required(CONF_PIN): pins.output_pin, + } + ), + validate, +) def to_code(config): @@ -52,6 +57,7 @@ def to_code(config): rgb_order = None if CONF_RGB_ORDER in config: rgb_order = cg.RawExpression(config[CONF_RGB_ORDER]) - template_args = cg.TemplateArguments(cg.RawExpression(config[CONF_CHIPSET]), - config[CONF_PIN], rgb_order) + template_args = cg.TemplateArguments( + cg.RawExpression(config[CONF_CHIPSET]), config[CONF_PIN], rgb_order + ) cg.add(var.add_leds(template_args, config[CONF_NUM_LEDS])) diff --git a/esphome/components/fastled_spi/light.py b/esphome/components/fastled_spi/light.py index ef14c0573889..11e0a8159c75 100644 --- a/esphome/components/fastled_spi/light.py +++ b/esphome/components/fastled_spi/light.py @@ -2,34 +2,44 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import fastled_base -from esphome.const import CONF_CHIPSET, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_DATA_RATE, \ - CONF_NUM_LEDS, CONF_RGB_ORDER +from esphome.const import ( + CONF_CHIPSET, + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_DATA_RATE, + CONF_NUM_LEDS, + CONF_RGB_ORDER, +) -AUTO_LOAD = ['fastled_base'] +AUTO_LOAD = ["fastled_base"] CHIPSETS = [ - 'LPD8806', - 'WS2801', - 'WS2803', - 'SM16716', - 'P9813', - 'APA102', - 'SK9822', - 'DOTSTAR', + "LPD8806", + "WS2801", + "WS2803", + "SM16716", + "P9813", + "APA102", + "SK9822", + "DOTSTAR", ] -CONFIG_SCHEMA = fastled_base.BASE_SCHEMA.extend({ - cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), - cv.Required(CONF_DATA_PIN): pins.output_pin, - cv.Required(CONF_CLOCK_PIN): pins.output_pin, - cv.Optional(CONF_DATA_RATE): cv.frequency, -}) +CONFIG_SCHEMA = fastled_base.BASE_SCHEMA.extend( + { + cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), + cv.Required(CONF_DATA_PIN): pins.output_pin, + cv.Required(CONF_CLOCK_PIN): pins.output_pin, + cv.Optional(CONF_DATA_RATE): cv.frequency, + } +) def to_code(config): var = yield fastled_base.new_fastled_light(config) - rgb_order = cg.RawExpression(config[CONF_RGB_ORDER] if CONF_RGB_ORDER in config else "RGB") + rgb_order = cg.RawExpression( + config[CONF_RGB_ORDER] if CONF_RGB_ORDER in config else "RGB" + ) data_rate = None if CONF_DATA_RATE in config: @@ -39,7 +49,11 @@ def to_code(config): else: data_rate_mhz = int(data_rate_khz / 1000) data_rate = cg.RawExpression(f"DATA_RATE_MHZ({data_rate_mhz})") - template_args = cg.TemplateArguments(cg.RawExpression(config[CONF_CHIPSET]), - config[CONF_DATA_PIN], config[CONF_CLOCK_PIN], rgb_order, - data_rate) + template_args = cg.TemplateArguments( + cg.RawExpression(config[CONF_CHIPSET]), + config[CONF_DATA_PIN], + config[CONF_CLOCK_PIN], + rgb_order, + data_rate, + ) cg.add(var.add_leds(template_args, config[CONF_NUM_LEDS])) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index ee50b10830be..e79d311dab98 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -7,11 +7,11 @@ from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE from esphome.core import CORE, HexInt -DEPENDENCIES = ['display'] +DEPENDENCIES = ["display"] MULTI_CONF = True -Font = display.display_ns.class_('Font') -Glyph = display.display_ns.class_('Glyph') +Font = display.display_ns.class_("Font") +Glyph = display.display_ns.class_("Glyph") def validate_glyphs(value): @@ -20,8 +20,8 @@ def validate_glyphs(value): value = cv.Schema([cv.string])(list(value)) def comparator(x, y): - x_ = x.encode('utf-8') - y_ = y.encode('utf-8') + x_ = x.encode("utf-8") + y_ = y.encode("utf-8") for c in range(min(len(x_), len(y_))): if x_[c] < y_[c]: @@ -43,36 +43,48 @@ def validate_pillow_installed(value): try: import PIL except ImportError as err: - raise cv.Invalid("Please install the pillow python package to use this feature. " - "(pip install pillow)") from err + raise cv.Invalid( + "Please install the pillow python package to use this feature. " + "(pip install pillow)" + ) from err - if PIL.__version__[0] < '4': - raise cv.Invalid("Please update your pillow installation to at least 4.0.x. " - "(pip install -U pillow)") + if PIL.__version__[0] < "4": + raise cv.Invalid( + "Please update your pillow installation to at least 4.0.x. " + "(pip install -U pillow)" + ) return value def validate_truetype_file(value): - if value.endswith('.zip'): # for Google Fonts downloads - raise cv.Invalid("Please unzip the font archive '{}' first and then use the .ttf files " - "inside.".format(value)) - if not value.endswith('.ttf'): - raise cv.Invalid("Only truetype (.ttf) files are supported. Please make sure you're " - "using the correct format or rename the extension to .ttf") + if value.endswith(".zip"): # for Google Fonts downloads + raise cv.Invalid( + "Please unzip the font archive '{}' first and then use the .ttf files " + "inside.".format(value) + ) + if not value.endswith(".ttf"): + raise cv.Invalid( + "Only truetype (.ttf) files are supported. Please make sure you're " + "using the correct format or rename the extension to .ttf" + ) return cv.file_(value) -DEFAULT_GLYPHS = ' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' -CONF_RAW_DATA_ID = 'raw_data_id' +DEFAULT_GLYPHS = ( + ' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' +) +CONF_RAW_DATA_ID = "raw_data_id" -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.int_range(min=1), - cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), -}) +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.int_range(min=1), + cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), + } +) CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA) @@ -91,7 +103,7 @@ def to_code(config): glyph_args = {} data = [] for glyph in config[CONF_GLYPHS]: - mask = font.getmask(glyph, mode='1') + mask = font.getmask(glyph, mode="1") _, (offset_x, offset_y) = font.font.getsize(glyph) width, height = mask.size width8 = ((width + 7) // 8) * 8 diff --git a/esphome/components/fujitsu_general/climate.py b/esphome/components/fujitsu_general/climate.py index a6774c397ab3..d58049e3f2b9 100644 --- a/esphome/components/fujitsu_general/climate.py +++ b/esphome/components/fujitsu_general/climate.py @@ -3,14 +3,18 @@ from esphome.components import climate_ir from esphome.const import CONF_ID -AUTO_LOAD = ['climate_ir'] +AUTO_LOAD = ["climate_ir"] -fujitsu_general_ns = cg.esphome_ns.namespace('fujitsu_general') -FujitsuGeneralClimate = fujitsu_general_ns.class_('FujitsuGeneralClimate', climate_ir.ClimateIR) +fujitsu_general_ns = cg.esphome_ns.namespace("fujitsu_general") +FujitsuGeneralClimate = fujitsu_general_ns.class_( + "FujitsuGeneralClimate", climate_ir.ClimateIR +) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(FujitsuGeneralClimate), -}) +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(FujitsuGeneralClimate), + } +) def to_code(config): diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index f6114642485a..75ee3f708bbe 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -3,186 +3,207 @@ namespace esphome { namespace fujitsu_general { -static const char *TAG = "fujitsu_general.climate"; +// bytes' bits are reversed for fujitsu, so nibbles are ordered 1, 0, 3, 2, 5, 4, etc... -// Control packet -const uint16_t FUJITSU_GENERAL_STATE_LENGTH = 16; +#define SET_NIBBLE(message, nibble, value) (message[nibble / 2] |= (value & 0b00001111) << ((nibble % 2) ? 0 : 4)) +#define GET_NIBBLE(message, nibble) ((message[nibble / 2] >> ((nibble % 2) ? 0 : 4)) & 0b00001111) -const uint8_t FUJITSU_GENERAL_BASE_BYTE0 = 0x14; -const uint8_t FUJITSU_GENERAL_BASE_BYTE1 = 0x63; -const uint8_t FUJITSU_GENERAL_BASE_BYTE2 = 0x00; -const uint8_t FUJITSU_GENERAL_BASE_BYTE3 = 0x10; -const uint8_t FUJITSU_GENERAL_BASE_BYTE4 = 0x10; -const uint8_t FUJITSU_GENERAL_BASE_BYTE5 = 0xFE; -const uint8_t FUJITSU_GENERAL_BASE_BYTE6 = 0x09; -const uint8_t FUJITSU_GENERAL_BASE_BYTE7 = 0x30; +static const char* TAG = "fujitsu_general.climate"; -// Temperature and POWER ON -const uint8_t FUJITSU_GENERAL_POWER_ON_MASK_BYTE8 = 0b00000001; -const uint8_t FUJITSU_GENERAL_BASE_BYTE8 = 0x40; +// Common header +const uint8_t FUJITSU_GENERAL_COMMON_LENGTH = 6; +const uint8_t FUJITSU_GENERAL_COMMON_BYTE0 = 0x14; +const uint8_t FUJITSU_GENERAL_COMMON_BYTE1 = 0x63; +const uint8_t FUJITSU_GENERAL_COMMON_BYTE2 = 0x00; +const uint8_t FUJITSU_GENERAL_COMMON_BYTE3 = 0x10; +const uint8_t FUJITSU_GENERAL_COMMON_BYTE4 = 0x10; +const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_BYTE = 5; + +// State message - temp & fan etc. +const uint8_t FUJITSU_GENERAL_STATE_MESSAGE_LENGTH = 16; +const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_STATE = 0xFE; + +// Util messages - off & eco etc. +const uint8_t FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH = 7; +const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_OFF = 0x02; +const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_ECONOMY = 0x09; +const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_NUDGE = 0x6C; + +// State header +const uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE0 = 0x09; +const uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE1 = 0x30; + +// State footer +const uint8_t FUJITSU_GENERAL_STATE_FOOTER_BYTE0 = 0x20; + +// Temperature +const uint8_t FUJITSU_GENERAL_TEMPERATURE_NIBBLE = 16; + +// Power on +const uint8_t FUJITSU_GENERAL_POWER_ON_NIBBLE = 17; +const uint8_t FUJITSU_GENERAL_POWER_ON = 0x01; +const uint8_t FUJITSU_GENERAL_POWER_OFF = 0x00; // Mode -const uint8_t FUJITSU_GENERAL_MODE_AUTO_BYTE9 = 0x00; -const uint8_t FUJITSU_GENERAL_MODE_HEAT_BYTE9 = 0x04; -const uint8_t FUJITSU_GENERAL_MODE_COOL_BYTE9 = 0x01; -const uint8_t FUJITSU_GENERAL_MODE_DRY_BYTE9 = 0x02; -const uint8_t FUJITSU_GENERAL_MODE_FAN_BYTE9 = 0x03; -const uint8_t FUJITSU_GENERAL_MODE_10C_BYTE9 = 0x0B; -const uint8_t FUJITSU_GENERAL_BASE_BYTE9 = 0x01; - -// Fan speed and swing -const uint8_t FUJITSU_GENERAL_FAN_AUTO_BYTE10 = 0x00; -const uint8_t FUJITSU_GENERAL_FAN_HIGH_BYTE10 = 0x01; -const uint8_t FUJITSU_GENERAL_FAN_MEDIUM_BYTE10 = 0x02; -const uint8_t FUJITSU_GENERAL_FAN_LOW_BYTE10 = 0x03; -const uint8_t FUJITSU_GENERAL_FAN_SILENT_BYTE10 = 0x04; -const uint8_t FUJITSU_GENERAL_SWING_NONE_BYTE10 = 0x00; -const uint8_t FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 = 0x01; -const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 = 0x02; -const uint8_t FUJITSU_GENERAL_SWING_BOTH_BYTE10 = 0x03; -const uint8_t FUJITSU_GENERAL_BASE_BYTE10 = 0x00; - -const uint8_t FUJITSU_GENERAL_BASE_BYTE11 = 0x00; -const uint8_t FUJITSU_GENERAL_BASE_BYTE12 = 0x00; -const uint8_t FUJITSU_GENERAL_BASE_BYTE13 = 0x00; - -// Outdoor Unit Low Noise -const uint8_t FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14 = 0xA0; -const uint8_t FUJITSU_GENERAL_BASE_BYTE14 = 0x20; - -// CRC -const uint8_t FUJITSU_GENERAL_BASE_BYTE15 = 0x6F; - -// Power off packet is specific -const uint16_t FUJITSU_GENERAL_OFF_LENGTH = 7; - -const uint8_t FUJITSU_GENERAL_OFF_BYTE0 = FUJITSU_GENERAL_BASE_BYTE0; -const uint8_t FUJITSU_GENERAL_OFF_BYTE1 = FUJITSU_GENERAL_BASE_BYTE1; -const uint8_t FUJITSU_GENERAL_OFF_BYTE2 = FUJITSU_GENERAL_BASE_BYTE2; -const uint8_t FUJITSU_GENERAL_OFF_BYTE3 = FUJITSU_GENERAL_BASE_BYTE3; -const uint8_t FUJITSU_GENERAL_OFF_BYTE4 = FUJITSU_GENERAL_BASE_BYTE4; -const uint8_t FUJITSU_GENERAL_OFF_BYTE5 = 0x02; -const uint8_t FUJITSU_GENERAL_OFF_BYTE6 = 0xFD; - -const uint8_t FUJITSU_GENERAL_TEMP_MAX = 30; // Celsius -const uint8_t FUJITSU_GENERAL_TEMP_MIN = 16; // Celsius +const uint8_t FUJITSU_GENERAL_MODE_NIBBLE = 19; +const uint8_t FUJITSU_GENERAL_MODE_AUTO = 0x00; +const uint8_t FUJITSU_GENERAL_MODE_HEAT = 0x04; +const uint8_t FUJITSU_GENERAL_MODE_COOL = 0x01; +const uint8_t FUJITSU_GENERAL_MODE_DRY = 0x02; +const uint8_t FUJITSU_GENERAL_MODE_FAN = 0x03; +// const uint8_t FUJITSU_GENERAL_MODE_10C = 0x0B; + +// Swing +const uint8_t FUJITSU_GENERAL_FAN_NIBBLE = 20; +const uint8_t FUJITSU_GENERAL_FAN_AUTO = 0x00; +const uint8_t FUJITSU_GENERAL_FAN_HIGH = 0x01; +const uint8_t FUJITSU_GENERAL_FAN_MEDIUM = 0x02; +const uint8_t FUJITSU_GENERAL_FAN_LOW = 0x03; +const uint8_t FUJITSU_GENERAL_FAN_SILENT = 0x04; + +// Fan speed +const uint8_t FUJITSU_GENERAL_SWING_NIBBLE = 21; +const uint8_t FUJITSU_GENERAL_SWING_NONE = 0x00; +const uint8_t FUJITSU_GENERAL_SWING_VERTICAL = 0x01; +const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL = 0x02; +const uint8_t FUJITSU_GENERAL_SWING_BOTH = 0x03; + +// TODO Outdoor Unit Low Noise +// const uint8_t FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14 = 0xA0; +// const uint8_t FUJITSU_GENERAL_STATE_BYTE14 = 0x20; const uint16_t FUJITSU_GENERAL_HEADER_MARK = 3300; const uint16_t FUJITSU_GENERAL_HEADER_SPACE = 1600; + const uint16_t FUJITSU_GENERAL_BIT_MARK = 420; const uint16_t FUJITSU_GENERAL_ONE_SPACE = 1200; const uint16_t FUJITSU_GENERAL_ZERO_SPACE = 420; + const uint16_t FUJITSU_GENERAL_TRL_MARK = 420; const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000; const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY = 38000; -FujitsuGeneralClimate::FujitsuGeneralClimate() - : ClimateIR( - FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1.0f, true, true, - {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, - {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL, - climate::CLIMATE_SWING_BOTH}) {} - void FujitsuGeneralClimate::transmit_state() { if (this->mode == climate::CLIMATE_MODE_OFF) { this->transmit_off_(); return; } - uint8_t remote_state[FUJITSU_GENERAL_STATE_LENGTH] = {0}; - - remote_state[0] = FUJITSU_GENERAL_BASE_BYTE0; - remote_state[1] = FUJITSU_GENERAL_BASE_BYTE1; - remote_state[2] = FUJITSU_GENERAL_BASE_BYTE2; - remote_state[3] = FUJITSU_GENERAL_BASE_BYTE3; - remote_state[4] = FUJITSU_GENERAL_BASE_BYTE4; - remote_state[5] = FUJITSU_GENERAL_BASE_BYTE5; - remote_state[6] = FUJITSU_GENERAL_BASE_BYTE6; - remote_state[7] = FUJITSU_GENERAL_BASE_BYTE7; - remote_state[8] = FUJITSU_GENERAL_BASE_BYTE8; - remote_state[9] = FUJITSU_GENERAL_BASE_BYTE9; - remote_state[10] = FUJITSU_GENERAL_BASE_BYTE10; - remote_state[11] = FUJITSU_GENERAL_BASE_BYTE11; - remote_state[12] = FUJITSU_GENERAL_BASE_BYTE12; - remote_state[13] = FUJITSU_GENERAL_BASE_BYTE13; - remote_state[14] = FUJITSU_GENERAL_BASE_BYTE14; - remote_state[15] = FUJITSU_GENERAL_BASE_BYTE15; + + ESP_LOGV(TAG, "Transmit state"); + + uint8_t remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0}; + + // Common message header + remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0; + remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1; + remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2; + remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3; + remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4; + remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_STATE; + remote_state[6] = FUJITSU_GENERAL_STATE_HEADER_BYTE0; + remote_state[7] = FUJITSU_GENERAL_STATE_HEADER_BYTE1; + + // unknown, does not appear to change with any remote settings + remote_state[14] = FUJITSU_GENERAL_STATE_FOOTER_BYTE0; // Set temperature - auto safecelsius = + uint8_t temperature_clamped = (uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX)); - remote_state[8] = (byte) safecelsius - 16; - remote_state[8] = remote_state[8] << 4; + uint8_t temperature_offset = temperature_clamped - FUJITSU_GENERAL_TEMP_MIN; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_TEMPERATURE_NIBBLE, temperature_offset); - // If not powered - set power on flag + // Set power on if (!this->power_) { - remote_state[8] = (byte) remote_state[8] | FUJITSU_GENERAL_POWER_ON_MASK_BYTE8; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_POWER_ON_NIBBLE, FUJITSU_GENERAL_POWER_ON); } // Set mode switch (this->mode) { case climate::CLIMATE_MODE_COOL: - remote_state[9] = FUJITSU_GENERAL_MODE_COOL_BYTE9; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_COOL); break; case climate::CLIMATE_MODE_HEAT: - remote_state[9] = FUJITSU_GENERAL_MODE_HEAT_BYTE9; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_HEAT); break; case climate::CLIMATE_MODE_DRY: - remote_state[9] = FUJITSU_GENERAL_MODE_DRY_BYTE9; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_DRY); break; case climate::CLIMATE_MODE_FAN_ONLY: - remote_state[9] = FUJITSU_GENERAL_MODE_FAN_BYTE9; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN); break; case climate::CLIMATE_MODE_AUTO: default: - remote_state[9] = FUJITSU_GENERAL_MODE_AUTO_BYTE9; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO); break; - // TODO: CLIMATE_MODE_10C are missing in esphome + // TODO: CLIMATE_MODE_10C is missing from esphome } // Set fan switch (this->fan_mode) { case climate::CLIMATE_FAN_HIGH: - remote_state[10] = FUJITSU_GENERAL_FAN_HIGH_BYTE10; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_HIGH); break; case climate::CLIMATE_FAN_MEDIUM: - remote_state[10] = FUJITSU_GENERAL_FAN_MEDIUM_BYTE10; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_MEDIUM); break; case climate::CLIMATE_FAN_LOW: - remote_state[10] = FUJITSU_GENERAL_FAN_LOW_BYTE10; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_LOW); break; case climate::CLIMATE_FAN_AUTO: default: - remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_AUTO); break; + // TODO Quiet / Silent } // Set swing switch (this->swing_mode) { case climate::CLIMATE_SWING_VERTICAL: - remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 << 4); + SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_VERTICAL); break; case climate::CLIMATE_SWING_HORIZONTAL: - remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 << 4); + SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_HORIZONTAL); break; case climate::CLIMATE_SWING_BOTH: - remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_BOTH_BYTE10 << 4); + SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_BOTH); break; case climate::CLIMATE_SWING_OFF: default: - remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_NONE_BYTE10 << 4); + SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_NONE); break; } // TODO: missing support for outdoor unit low noise // remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14; - // CRC - remote_state[15] = 0; - for (int i = 7; i < 15; i++) { - remote_state[15] += (byte) remote_state[i]; // Addiction - } - remote_state[15] = 0x100 - remote_state[15]; // mod 256 + remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1] = this->checksum_state_(remote_state); + + this->transmit_(remote_state, FUJITSU_GENERAL_STATE_MESSAGE_LENGTH); + + this->power_ = true; +} + +void FujitsuGeneralClimate::transmit_off_() { + ESP_LOGV(TAG, "Transmit off"); + + uint8_t remote_state[FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH] = {0}; + + remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0; + remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1; + remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2; + remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3; + remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4; + remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_OFF; + remote_state[6] = this->checksum_util_(remote_state); + + this->transmit_(remote_state, FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH); + + this->power_ = false; +} + +void FujitsuGeneralClimate::transmit_(uint8_t const* message, uint8_t length) { + ESP_LOGV(TAG, "Transmit message length %d", length); auto transmit = this->transmitter_->transmit(); auto data = transmit.get_data(); @@ -192,62 +213,193 @@ void FujitsuGeneralClimate::transmit_state() { // Header data->mark(FUJITSU_GENERAL_HEADER_MARK); data->space(FUJITSU_GENERAL_HEADER_SPACE); + // Data - for (uint8_t i : remote_state) { - // Send all Bits from Byte Data in Reverse Order - for (uint8_t mask = 00000001; mask > 0; mask <<= 1) { // iterate through bit mask + for (uint8_t i = 0; i < length; ++i) { + const uint8_t byte = message[i]; + for (uint8_t mask = 0b00000001; mask > 0; mask <<= 1) { // write from right to left data->mark(FUJITSU_GENERAL_BIT_MARK); - bool bit = i & mask; + bool bit = byte & mask; data->space(bit ? FUJITSU_GENERAL_ONE_SPACE : FUJITSU_GENERAL_ZERO_SPACE); - // Next bits } } + // Footer data->mark(FUJITSU_GENERAL_TRL_MARK); data->space(FUJITSU_GENERAL_TRL_SPACE); transmit.perform(); +} - this->power_ = true; +uint8_t FujitsuGeneralClimate::checksum_state_(uint8_t const* message) { + uint8_t checksum = 0; + for (uint8_t i = 7; i < FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1; ++i) { + checksum += message[i]; + } + return 256 - checksum; } -void FujitsuGeneralClimate::transmit_off_() { - uint8_t remote_state[FUJITSU_GENERAL_OFF_LENGTH] = {0}; +uint8_t FujitsuGeneralClimate::checksum_util_(uint8_t const* message) { return 255 - message[5]; } - remote_state[0] = FUJITSU_GENERAL_OFF_BYTE0; - remote_state[1] = FUJITSU_GENERAL_OFF_BYTE1; - remote_state[2] = FUJITSU_GENERAL_OFF_BYTE2; - remote_state[3] = FUJITSU_GENERAL_OFF_BYTE3; - remote_state[4] = FUJITSU_GENERAL_OFF_BYTE4; - remote_state[5] = FUJITSU_GENERAL_OFF_BYTE5; - remote_state[6] = FUJITSU_GENERAL_OFF_BYTE6; +bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) { + ESP_LOGV(TAG, "Received IR message"); - auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + // Validate header + if (!data.expect_item(FUJITSU_GENERAL_HEADER_MARK, FUJITSU_GENERAL_HEADER_SPACE)) { + ESP_LOGV(TAG, "Header fail"); + return false; + } - data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY); + uint8_t recv_message[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0}; + + // Read header + for (uint8_t byte = 0; byte < FUJITSU_GENERAL_COMMON_LENGTH; ++byte) { + // Read bit + for (uint8_t bit = 0; bit < 8; ++bit) { + if (data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ONE_SPACE)) { + recv_message[byte] |= 1 << bit; // read from right to left + } else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) { + ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit); + return false; + } + } + } - // Header - data->mark(FUJITSU_GENERAL_HEADER_MARK); - data->space(FUJITSU_GENERAL_HEADER_SPACE); + const uint8_t recv_message_type = recv_message[FUJITSU_GENERAL_MESSAGE_TYPE_BYTE]; + uint8_t recv_message_length; - // Data - for (uint8_t i : remote_state) { - // Send all Bits from Byte Data in Reverse Order - for (uint8_t mask = 00000001; mask > 0; mask <<= 1) { // iterate through bit mask - data->mark(FUJITSU_GENERAL_BIT_MARK); - bool bit = i & mask; - data->space(bit ? FUJITSU_GENERAL_ONE_SPACE : FUJITSU_GENERAL_ZERO_SPACE); - // Next bits + switch (recv_message_type) { + case FUJITSU_GENERAL_MESSAGE_TYPE_STATE: + ESP_LOGV(TAG, "Received state message"); + recv_message_length = FUJITSU_GENERAL_STATE_MESSAGE_LENGTH; + break; + case FUJITSU_GENERAL_MESSAGE_TYPE_OFF: + case FUJITSU_GENERAL_MESSAGE_TYPE_ECONOMY: + case FUJITSU_GENERAL_MESSAGE_TYPE_NUDGE: + ESP_LOGV(TAG, "Received util message"); + recv_message_length = FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH; + break; + default: + ESP_LOGV(TAG, "Unknown message type %X", recv_message_type); + return false; + } + + // Read message body + for (uint8_t byte = FUJITSU_GENERAL_COMMON_LENGTH; byte < recv_message_length; ++byte) { + for (uint8_t bit = 0; bit < 8; ++bit) { + if (data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ONE_SPACE)) { + recv_message[byte] |= 1 << bit; // read from right to left + } else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) { + ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit); + return false; + } } } - // Footer - data->mark(FUJITSU_GENERAL_TRL_MARK); - data->space(FUJITSU_GENERAL_TRL_SPACE); - transmit.perform(); + // Validate footer + if (!data.expect_mark(FUJITSU_GENERAL_BIT_MARK)) { + ESP_LOGV(TAG, "Footer fail"); + return false; + } - this->power_ = false; + for (uint8_t byte = 0; byte < recv_message_length; ++byte) { + ESP_LOGVV(TAG, "%02X", recv_message[byte]); + } + + const uint8_t recv_checksum = recv_message[recv_message_length - 1]; + uint8_t calculated_checksum; + if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) { + calculated_checksum = this->checksum_state_(recv_message); + } else { + calculated_checksum = this->checksum_util_(recv_message); + } + + if (recv_checksum != calculated_checksum) { + ESP_LOGV(TAG, "Checksum fail - expected %X - got %X", calculated_checksum, recv_checksum); + return false; + } + + if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) { + const uint8_t recv_tempertature = GET_NIBBLE(recv_message, FUJITSU_GENERAL_TEMPERATURE_NIBBLE); + const uint8_t offset_temperature = recv_tempertature + FUJITSU_GENERAL_TEMP_MIN; + this->target_temperature = offset_temperature; + ESP_LOGV(TAG, "Received temperature %d", offset_temperature); + + const uint8_t recv_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_MODE_NIBBLE); + ESP_LOGV(TAG, "Received mode %X", recv_mode); + switch (recv_mode) { + case FUJITSU_GENERAL_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + case FUJITSU_GENERAL_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + case FUJITSU_GENERAL_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + case FUJITSU_GENERAL_MODE_FAN: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + case FUJITSU_GENERAL_MODE_AUTO: + default: + // TODO: CLIMATE_MODE_10C is missing from esphome + this->mode = climate::CLIMATE_MODE_AUTO; + break; + } + + const uint8_t recv_fan_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_FAN_NIBBLE); + ESP_LOGV(TAG, "Received fan mode %X", recv_fan_mode); + switch (recv_fan_mode) { + // TODO No Quiet / Silent in ESPH + case FUJITSU_GENERAL_FAN_SILENT: + case FUJITSU_GENERAL_FAN_LOW: + this->fan_mode = climate::CLIMATE_FAN_LOW; + break; + case FUJITSU_GENERAL_FAN_MEDIUM: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + break; + case FUJITSU_GENERAL_FAN_HIGH: + this->fan_mode = climate::CLIMATE_FAN_HIGH; + break; + case FUJITSU_GENERAL_FAN_AUTO: + default: + this->fan_mode = climate::CLIMATE_FAN_AUTO; + break; + } + + const uint8_t recv_swing_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_SWING_NIBBLE); + ESP_LOGV(TAG, "Received swing mode %X", recv_swing_mode); + switch (recv_swing_mode) { + case FUJITSU_GENERAL_SWING_VERTICAL: + this->swing_mode = climate::CLIMATE_SWING_VERTICAL; + break; + case FUJITSU_GENERAL_SWING_HORIZONTAL: + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + break; + case FUJITSU_GENERAL_SWING_BOTH: + this->swing_mode = climate::CLIMATE_SWING_BOTH; + break; + case FUJITSU_GENERAL_SWING_NONE: + default: + this->swing_mode = climate::CLIMATE_SWING_OFF; + } + + this->power_ = true; + } + + else if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_OFF) { + ESP_LOGV(TAG, "Received off message"); + this->mode = climate::CLIMATE_MODE_OFF; + this->power_ = false; + } + + else { + ESP_LOGV(TAG, "Received unsupprted message type %X", recv_message_type); + return false; + } + + this->publish_state(); + return true; } } // namespace fujitsu_general diff --git a/esphome/components/fujitsu_general/fujitsu_general.h b/esphome/components/fujitsu_general/fujitsu_general.h index 80db81a16748..8154d7a1d24f 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.h +++ b/esphome/components/fujitsu_general/fujitsu_general.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/log.h" #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/components/climate_ir/climate_ir.h" @@ -7,9 +8,17 @@ namespace esphome { namespace fujitsu_general { +const uint8_t FUJITSU_GENERAL_TEMP_MIN = 16; // Celsius // TODO 16 for heating, 18 for cooling, unsupported in ESPH +const uint8_t FUJITSU_GENERAL_TEMP_MAX = 30; // Celsius + class FujitsuGeneralClimate : public climate_ir::ClimateIR { public: - FujitsuGeneralClimate(); + FujitsuGeneralClimate() + : ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL, + climate::CLIMATE_SWING_BOTH}) {} protected: /// Transmit via IR the state of this climate controller. @@ -17,6 +26,19 @@ class FujitsuGeneralClimate : public climate_ir::ClimateIR { /// Transmit via IR power off command. void transmit_off_(); + /// Parse incomming message + bool on_receive(remote_base::RemoteReceiveData data) override; + + /// Transmit message as IR pulses + void transmit_(uint8_t const* message, uint8_t length); + + /// Calculate checksum for a state message + uint8_t checksum_state_(uint8_t const* message); + + /// Calculate cecksum for a util message + uint8_t checksum_util_(uint8_t const* message); + + // true if currently on - fujitsus transmit an on flag on when the remote moves from off to on bool power_{false}; }; diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index f1c4f4faf2c9..9ec3bc17cea4 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -2,21 +2,29 @@ from esphome import config_validation as cv, automation from esphome import codegen as cg -from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE, CONF_VALUE +from esphome.const import ( + CONF_ID, + CONF_INITIAL_VALUE, + CONF_RESTORE_VALUE, + CONF_TYPE, + CONF_VALUE, +) from esphome.core import coroutine_with_priority -CODEOWNERS = ['@esphome/core'] -globals_ns = cg.esphome_ns.namespace('globals') -GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component) -GlobalVarSetAction = globals_ns.class_('GlobalVarSetAction', automation.Action) +CODEOWNERS = ["@esphome/core"] +globals_ns = cg.esphome_ns.namespace("globals") +GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) +GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action) MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(GlobalsComponent), - cv.Required(CONF_TYPE): cv.string_strict, - cv.Optional(CONF_INITIAL_VALUE): cv.string_strict, - cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(GlobalsComponent), + cv.Required(CONF_TYPE): cv.string_strict, + cv.Optional(CONF_INITIAL_VALUE): cv.string_strict, + cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, + } +).extend(cv.COMPONENT_SCHEMA) # Run with low priority so that namespaces are registered first @@ -42,15 +50,22 @@ def to_code(config): cg.add(glob.set_restore_value(hash_)) -@automation.register_action('globals.set', GlobalVarSetAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(GlobalsComponent), - cv.Required(CONF_VALUE): cv.templatable(cv.string_strict), -})) +@automation.register_action( + "globals.set", + GlobalVarSetAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(GlobalsComponent), + cv.Required(CONF_VALUE): cv.templatable(cv.string_strict), + } + ), +) def globals_set_to_code(config, action_id, template_arg, args): full_id, paren = yield cg.get_variable_with_full_id(config[CONF_ID]) template_arg = cg.TemplateArguments(full_id.type, *template_arg) var = cg.new_Pvariable(action_id, template_arg, paren) - templ = yield cg.templatable(config[CONF_VALUE], args, None, - to_exp=cg.RawExpression) + templ = yield cg.templatable( + config[CONF_VALUE], args, None, to_exp=cg.RawExpression + ) cg.add(var.set_value(templ)) yield var diff --git a/esphome/components/gpio/__init__.py b/esphome/components/gpio/__init__.py index c36ba8f43304..07ebb64cd23f 100644 --- a/esphome/components/gpio/__init__.py +++ b/esphome/components/gpio/__init__.py @@ -1,4 +1,4 @@ import esphome.codegen as cg -CODEOWNERS = ['@esphome/core'] -gpio_ns = cg.esphome_ns.namespace('gpio') +CODEOWNERS = ["@esphome/core"] +gpio_ns = cg.esphome_ns.namespace("gpio") diff --git a/esphome/components/gpio/binary_sensor/__init__.py b/esphome/components/gpio/binary_sensor/__init__.py index e269de5a71aa..4a24efcdb0ce 100644 --- a/esphome/components/gpio/binary_sensor/__init__.py +++ b/esphome/components/gpio/binary_sensor/__init__.py @@ -5,12 +5,16 @@ from esphome.const import CONF_ID, CONF_PIN from .. import gpio_ns -GPIOBinarySensor = gpio_ns.class_('GPIOBinarySensor', binary_sensor.BinarySensor, cg.Component) +GPIOBinarySensor = gpio_ns.class_( + "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component +) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(GPIOBinarySensor), - cv.Required(CONF_PIN): pins.gpio_input_pin_schema -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(GPIOBinarySensor), + cv.Required(CONF_PIN): pins.gpio_input_pin_schema, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/gpio/output/__init__.py b/esphome/components/gpio/output/__init__.py index bab23c824b4d..d98a4905666b 100644 --- a/esphome/components/gpio/output/__init__.py +++ b/esphome/components/gpio/output/__init__.py @@ -5,13 +5,14 @@ from esphome.const import CONF_ID, CONF_PIN from .. import gpio_ns -GPIOBinaryOutput = gpio_ns.class_('GPIOBinaryOutput', output.BinaryOutput, - cg.Component) +GPIOBinaryOutput = gpio_ns.class_("GPIOBinaryOutput", output.BinaryOutput, cg.Component) -CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend({ - cv.Required(CONF_ID): cv.declare_id(GPIOBinaryOutput), - cv.Required(CONF_PIN): pins.gpio_output_pin_schema, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(GPIOBinaryOutput), + cv.Required(CONF_PIN): pins.gpio_output_pin_schema, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/gpio/switch/__init__.py b/esphome/components/gpio/switch/__init__.py index f75bc71009af..ebd12d10ca7f 100644 --- a/esphome/components/gpio/switch/__init__.py +++ b/esphome/components/gpio/switch/__init__.py @@ -5,25 +5,30 @@ from esphome.const import CONF_ID, CONF_INTERLOCK, CONF_PIN, CONF_RESTORE_MODE from .. import gpio_ns -GPIOSwitch = gpio_ns.class_('GPIOSwitch', switch.Switch, cg.Component) -GPIOSwitchRestoreMode = gpio_ns.enum('GPIOSwitchRestoreMode') +GPIOSwitch = gpio_ns.class_("GPIOSwitch", switch.Switch, cg.Component) +GPIOSwitchRestoreMode = gpio_ns.enum("GPIOSwitchRestoreMode") RESTORE_MODES = { - 'RESTORE_DEFAULT_OFF': GPIOSwitchRestoreMode.GPIO_SWITCH_RESTORE_DEFAULT_OFF, - 'RESTORE_DEFAULT_ON': GPIOSwitchRestoreMode.GPIO_SWITCH_RESTORE_DEFAULT_ON, - 'ALWAYS_OFF': GPIOSwitchRestoreMode.GPIO_SWITCH_ALWAYS_OFF, - 'ALWAYS_ON': GPIOSwitchRestoreMode.GPIO_SWITCH_ALWAYS_ON, + "RESTORE_DEFAULT_OFF": GPIOSwitchRestoreMode.GPIO_SWITCH_RESTORE_DEFAULT_OFF, + "RESTORE_DEFAULT_ON": GPIOSwitchRestoreMode.GPIO_SWITCH_RESTORE_DEFAULT_ON, + "ALWAYS_OFF": GPIOSwitchRestoreMode.GPIO_SWITCH_ALWAYS_OFF, + "ALWAYS_ON": GPIOSwitchRestoreMode.GPIO_SWITCH_ALWAYS_ON, } -CONF_INTERLOCK_WAIT_TIME = 'interlock_wait_time' -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(GPIOSwitch), - cv.Required(CONF_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_RESTORE_MODE, default='RESTORE_DEFAULT_OFF'): - cv.enum(RESTORE_MODES, upper=True, space='_'), - cv.Optional(CONF_INTERLOCK): cv.ensure_list(cv.use_id(switch.Switch)), - cv.Optional(CONF_INTERLOCK_WAIT_TIME, default='0ms'): cv.positive_time_period_milliseconds, -}).extend(cv.COMPONENT_SCHEMA) +CONF_INTERLOCK_WAIT_TIME = "interlock_wait_time" +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(GPIOSwitch), + cv.Required(CONF_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), + cv.Optional(CONF_INTERLOCK): cv.ensure_list(cv.use_id(switch.Switch)), + cv.Optional( + CONF_INTERLOCK_WAIT_TIME, default="0ms" + ): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index ddbd29d5f8ae..60dcc2002c5c 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -3,17 +3,23 @@ from esphome.components import uart from esphome.const import CONF_ID -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] -gps_ns = cg.esphome_ns.namespace('gps') -GPS = gps_ns.class_('GPS', cg.Component, uart.UARTDevice) -GPSListener = gps_ns.class_('GPSListener') +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' +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) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(GPS), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): @@ -22,4 +28,4 @@ def to_code(config): yield uart.register_uart_device(var, config) # https://platformio.org/lib/show/1655/TinyGPSPlus - cg.add_library('1655', '1.0.2') # TinyGPSPlus, has name conflict + cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict diff --git a/esphome/components/gps/time/__init__.py b/esphome/components/gps/time/__init__.py index 421d2e671732..24ea263f6c05 100644 --- a/esphome/components/gps/time/__init__.py +++ b/esphome/components/gps/time/__init__.py @@ -4,14 +4,18 @@ from esphome.const import CONF_ID from .. import gps_ns, GPSListener, CONF_GPS_ID, GPS -DEPENDENCIES = ['gps'] +DEPENDENCIES = ["gps"] -GPSTime = gps_ns.class_('GPSTime', cg.PollingComponent, time_.RealTimeClock, GPSListener) +GPSTime = gps_ns.class_( + "GPSTime", cg.PollingComponent, 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.polling_component_schema('5min')) +CONFIG_SCHEMA = time_.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(GPSTime), + cv.GenerateID(CONF_GPS_ID): cv.use_id(GPS), + } +).extend(cv.polling_component_schema("5min")) def to_code(config): diff --git a/esphome/components/hbridge/light.py b/esphome/components/hbridge/light.py index abaf7152d7d3..6c695fbd4aaa 100644 --- a/esphome/components/hbridge/light.py +++ b/esphome/components/hbridge/light.py @@ -3,14 +3,18 @@ from esphome.components import light, output from esphome.const import CONF_OUTPUT_ID, CONF_PIN_A, CONF_PIN_B -hbridge_ns = cg.esphome_ns.namespace('hbridge') -HBridgeLightOutput = hbridge_ns.class_('HBridgeLightOutput', cg.PollingComponent, light.LightOutput) +hbridge_ns = cg.esphome_ns.namespace("hbridge") +HBridgeLightOutput = hbridge_ns.class_( + "HBridgeLightOutput", cg.PollingComponent, light.LightOutput +) -CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(HBridgeLightOutput), - cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput), - cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput), -}) +CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(HBridgeLightOutput), + cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput), + cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput), + } +) def to_code(config): diff --git a/esphome/components/hdc1080/sensor.py b/esphome/components/hdc1080/sensor.py index 00b8296351c2..15a92cccf231 100644 --- a/esphome/components/hdc1080/sensor.py +++ b/esphome/components/hdc1080/sensor.py @@ -1,19 +1,39 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \ - ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_CELSIUS, UNIT_PERCENT +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -hdc1080_ns = cg.esphome_ns.namespace('hdc1080') -HDC1080Component = hdc1080_ns.class_('HDC1080Component', cg.PollingComponent, i2c.I2CDevice) +hdc1080_ns = cg.esphome_ns.namespace("hdc1080") +HDC1080Component = hdc1080_ns.class_( + "HDC1080Component", cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(HDC1080Component), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HDC1080Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)) +) def to_code(config): diff --git a/esphome/components/hitachi_ac344/climate.py b/esphome/components/hitachi_ac344/climate.py index fb1c21b2000c..9ae0cd39de00 100644 --- a/esphome/components/hitachi_ac344/climate.py +++ b/esphome/components/hitachi_ac344/climate.py @@ -3,14 +3,16 @@ from esphome.components import climate_ir from esphome.const import CONF_ID -AUTO_LOAD = ['climate_ir'] +AUTO_LOAD = ["climate_ir"] -hitachi_ac344_ns = cg.esphome_ns.namespace('hitachi_ac344') -HitachiClimate = hitachi_ac344_ns.class_('HitachiClimate', climate_ir.ClimateIR) +hitachi_ac344_ns = cg.esphome_ns.namespace("hitachi_ac344") +HitachiClimate = hitachi_ac344_ns.class_("HitachiClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(HitachiClimate), -}) +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HitachiClimate), + } +) def to_code(config): diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index e1a0ec6f18ba..28aa24d7cdce 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -2,40 +2,72 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor -from esphome.const import CONF_CHANGE_MODE_EVERY, CONF_INITIAL_MODE, CONF_CURRENT, \ - CONF_CURRENT_RESISTOR, CONF_ID, CONF_POWER, CONF_ENERGY, CONF_SEL_PIN, CONF_VOLTAGE, \ - CONF_VOLTAGE_DIVIDER, ICON_FLASH, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_WATT_HOURS +from esphome.const import ( + CONF_CHANGE_MODE_EVERY, + CONF_INITIAL_MODE, + CONF_CURRENT, + CONF_CURRENT_RESISTOR, + CONF_ID, + CONF_POWER, + CONF_ENERGY, + CONF_SEL_PIN, + CONF_VOLTAGE, + CONF_VOLTAGE_DIVIDER, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, + UNIT_WATT_HOURS, +) -AUTO_LOAD = ['pulse_counter'] +AUTO_LOAD = ["pulse_counter"] -hlw8012_ns = cg.esphome_ns.namespace('hlw8012') -HLW8012Component = hlw8012_ns.class_('HLW8012Component', cg.PollingComponent) -HLW8012InitialMode = hlw8012_ns.enum('HLW8012InitialMode') +hlw8012_ns = cg.esphome_ns.namespace("hlw8012") +HLW8012Component = hlw8012_ns.class_("HLW8012Component", cg.PollingComponent) +HLW8012InitialMode = hlw8012_ns.enum("HLW8012InitialMode") INITIAL_MODES = { CONF_CURRENT: HLW8012InitialMode.HLW8012_INITIAL_MODE_CURRENT, CONF_VOLTAGE: HLW8012InitialMode.HLW8012_INITIAL_MODE_VOLTAGE, } -CONF_CF1_PIN = 'cf1_pin' -CONF_CF_PIN = 'cf_pin' -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(HLW8012Component), - cv.Required(CONF_SEL_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CF_PIN): cv.All(pins.internal_gpio_input_pullup_pin_schema, - pins.validate_has_interrupt), - cv.Required(CONF_CF1_PIN): cv.All(pins.internal_gpio_input_pullup_pin_schema, - pins.validate_has_interrupt), - - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), - cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), - cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), - cv.Optional(CONF_ENERGY): sensor.sensor_schema(UNIT_WATT_HOURS, ICON_FLASH, 1), - - cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, - cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, - cv.Optional(CONF_CHANGE_MODE_EVERY, default=8): cv.All(cv.uint32_t, cv.Range(min=1)), - cv.Optional(CONF_INITIAL_MODE, default=CONF_VOLTAGE): cv.one_of(*INITIAL_MODES, lower=True), -}).extend(cv.polling_component_schema('60s')) +CONF_CF1_PIN = "cf1_pin" +CONF_CF_PIN = "cf_pin" +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(HLW8012Component), + cv.Required(CONF_SEL_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CF_PIN): cv.All( + pins.internal_gpio_input_pullup_pin_schema, pins.validate_has_interrupt + ), + cv.Required(CONF_CF1_PIN): cv.All( + pins.internal_gpio_input_pullup_pin_schema, pins.validate_has_interrupt + ), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + UNIT_WATT_HOURS, ICON_EMPTY, 1, DEVICE_CLASS_ENERGY + ), + cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, + cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, + cv.Optional(CONF_CHANGE_MODE_EVERY, default=8): cv.All( + cv.uint32_t, cv.Range(min=1) + ), + cv.Optional(CONF_INITIAL_MODE, default=CONF_VOLTAGE): cv.one_of( + *INITIAL_MODES, lower=True + ), + } +).extend(cv.polling_component_schema("60s")) def to_code(config): diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index ef7669bc0352..14ca2e77ba7f 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -1,22 +1,31 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, CONF_PM_2_5, CONF_PM_10_0, CONF_PM_1_0, \ - UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON - -DEPENDENCIES = ['i2c'] - -hm3301_ns = cg.esphome_ns.namespace('hm3301') -HM3301Component = hm3301_ns.class_('HM3301Component', cg.PollingComponent, i2c.I2CDevice) -AQICalculatorType = hm3301_ns.enum('AQICalculatorType') - -CONF_AQI = 'aqi' -CONF_CALCULATION_TYPE = 'calculation_type' -UNIT_INDEX = 'index' +from esphome.const import ( + CONF_ID, + CONF_PM_2_5, + CONF_PM_10_0, + CONF_PM_1_0, + DEVICE_CLASS_EMPTY, + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, +) + +DEPENDENCIES = ["i2c"] + +hm3301_ns = cg.esphome_ns.namespace("hm3301") +HM3301Component = hm3301_ns.class_( + "HM3301Component", cg.PollingComponent, i2c.I2CDevice +) +AQICalculatorType = hm3301_ns.enum("AQICalculatorType") + +CONF_AQI = "aqi" +CONF_CALCULATION_TYPE = "calculation_type" +UNIT_INDEX = "index" AQI_CALCULATION_TYPE = { - 'CAQI': AQICalculatorType.CAQI_TYPE, - 'AQI': AQICalculatorType.AQI_TYPE + "CAQI": AQICalculatorType.CAQI_TYPE, + "AQI": AQICalculatorType.AQI_TYPE, } @@ -28,20 +37,43 @@ def validate(config): return config -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(HM3301Component), - - cv.Optional(CONF_PM_1_0): - sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), - cv.Optional(CONF_PM_2_5): - sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), - cv.Optional(CONF_PM_10_0): - sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), - cv.Optional(CONF_AQI): - sensor.sensor_schema(UNIT_INDEX, ICON_CHEMICAL_WEAPON, 0).extend({ - cv.Required(CONF_CALCULATION_TYPE): cv.enum(AQI_CALCULATION_TYPE, upper=True), - }) -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40)), validate) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HM3301Component), + cv.Optional(CONF_PM_1_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_2_5): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_10_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_AQI): sensor.sensor_schema( + UNIT_INDEX, ICON_CHEMICAL_WEAPON, 0, DEVICE_CLASS_EMPTY + ).extend( + { + cv.Required(CONF_CALCULATION_TYPE): cv.enum( + AQI_CALCULATION_TYPE, upper=True + ), + } + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)), + validate, +) def to_code(config): @@ -67,4 +99,4 @@ def to_code(config): cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) # https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301 - cg.add_library('6306', '1.0.3') + cg.add_library("6306", "1.0.3") diff --git a/esphome/components/hmc5883l/sensor.py b/esphome/components/hmc5883l/sensor.py index b06328469834..d057caf030a3 100644 --- a/esphome/components/hmc5883l/sensor.py +++ b/esphome/components/hmc5883l/sensor.py @@ -1,22 +1,33 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import (CONF_ADDRESS, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, ICON_MAGNET, - UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION, - CONF_UPDATE_INTERVAL) - -DEPENDENCIES = ['i2c'] - -hmc5883l_ns = cg.esphome_ns.namespace('hmc5883l') - -CONF_FIELD_STRENGTH_X = 'field_strength_x' -CONF_FIELD_STRENGTH_Y = 'field_strength_y' -CONF_FIELD_STRENGTH_Z = 'field_strength_z' -CONF_HEADING = 'heading' - -HMC5883LComponent = hmc5883l_ns.class_('HMC5883LComponent', cg.PollingComponent, i2c.I2CDevice) - -HMC5883LOversampling = hmc5883l_ns.enum('HMC5883LOversampling') +from esphome.const import ( + CONF_ADDRESS, + CONF_ID, + CONF_OVERSAMPLING, + CONF_RANGE, + DEVICE_CLASS_EMPTY, + ICON_MAGNET, + UNIT_MICROTESLA, + UNIT_DEGREES, + ICON_SCREEN_ROTATION, + CONF_UPDATE_INTERVAL, +) + +DEPENDENCIES = ["i2c"] + +hmc5883l_ns = cg.esphome_ns.namespace("hmc5883l") + +CONF_FIELD_STRENGTH_X = "field_strength_x" +CONF_FIELD_STRENGTH_Y = "field_strength_y" +CONF_FIELD_STRENGTH_Z = "field_strength_z" +CONF_HEADING = "heading" + +HMC5883LComponent = hmc5883l_ns.class_( + "HMC5883LComponent", cg.PollingComponent, i2c.I2CDevice +) + +HMC5883LOversampling = hmc5883l_ns.enum("HMC5883LOversampling") HMC5883LOversamplings = { 1: HMC5883LOversampling.HMC5883L_OVERSAMPLING_1, 2: HMC5883LOversampling.HMC5883L_OVERSAMPLING_2, @@ -24,7 +35,7 @@ 8: HMC5883LOversampling.HMC5883L_OVERSAMPLING_8, } -HMC5883LDatarate = hmc5883l_ns.enum('HMC5883LDatarate') +HMC5883LDatarate = hmc5883l_ns.enum("HMC5883LDatarate") HMC5883LDatarates = { 0.75: HMC5883LDatarate.HMC5883L_DATARATE_0_75_HZ, 1.5: HMC5883LDatarate.HMC5883L_DATARATE_1_5_HZ, @@ -35,7 +46,7 @@ 75: HMC5883LDatarate.HMC5883L_DATARATE_75_0_HZ, } -HMC5883LRange = hmc5883l_ns.enum('HMC5883LRange') +HMC5883LRange = hmc5883l_ns.enum("HMC5883LRange") HMC5883L_RANGES = { 88: HMC5883LRange.HMC5883L_RANGE_88_UT, 130: HMC5883LRange.HMC5883L_RANGE_130_UT, @@ -59,30 +70,45 @@ def validate_enum_bound(value): value = cv.string(value) for unit in _units: if value.endswith(unit): - value = value[:-len(unit)] + value = value[: -len(unit)] break return enum_bound(value) - return validate_enum_bound + return validate_enum_bound -field_strength_schema = sensor.sensor_schema(UNIT_MICROTESLA, ICON_MAGNET, 1) -heading_schema = sensor.sensor_schema(UNIT_DEGREES, ICON_SCREEN_ROTATION, 1) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(HMC5883LComponent), - cv.Optional(CONF_ADDRESS): cv.i2c_address, - cv.Optional(CONF_OVERSAMPLING, default='1x'): validate_enum(HMC5883LOversamplings, units="x"), - cv.Optional(CONF_RANGE, default='130µT'): validate_enum(HMC5883L_RANGES, units=["uT", "µT"]), - cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema, - cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, - cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, - cv.Optional(CONF_HEADING): heading_schema, -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x1E)) +field_strength_schema = sensor.sensor_schema( + UNIT_MICROTESLA, ICON_MAGNET, 1, DEVICE_CLASS_EMPTY +) +heading_schema = sensor.sensor_schema( + UNIT_DEGREES, ICON_SCREEN_ROTATION, 1, DEVICE_CLASS_EMPTY +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HMC5883LComponent), + cv.Optional(CONF_ADDRESS): cv.i2c_address, + cv.Optional(CONF_OVERSAMPLING, default="1x"): validate_enum( + HMC5883LOversamplings, units="x" + ), + cv.Optional(CONF_RANGE, default="130µT"): validate_enum( + HMC5883L_RANGES, units=["uT", "µT"] + ), + cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema, + cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, + cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, + cv.Optional(CONF_HEADING): heading_schema, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x1E)) +) def auto_data_rate(config): interval_sec = config[CONF_UPDATE_INTERVAL].seconds - interval_hz = 1.0/interval_sec + interval_hz = 1.0 / interval_sec for datarate in sorted(HMC5883LDatarates.keys()): if float(datarate) >= interval_hz: return HMC5883LDatarates[datarate] diff --git a/esphome/components/homeassistant/__init__.py b/esphome/components/homeassistant/__init__.py index 69d759b97750..c151abc2507d 100644 --- a/esphome/components/homeassistant/__init__.py +++ b/esphome/components/homeassistant/__init__.py @@ -1,4 +1,4 @@ import esphome.codegen as cg -CODEOWNERS = ['@OttoWinter'] -homeassistant_ns = cg.esphome_ns.namespace('homeassistant') +CODEOWNERS = ["@OttoWinter"] +homeassistant_ns = cg.esphome_ns.namespace("homeassistant") diff --git a/esphome/components/homeassistant/binary_sensor/__init__.py b/esphome/components/homeassistant/binary_sensor/__init__.py index 88e2f2fcb296..850f239d6ff6 100644 --- a/esphome/components/homeassistant/binary_sensor/__init__.py +++ b/esphome/components/homeassistant/binary_sensor/__init__.py @@ -4,15 +4,17 @@ from esphome.const import CONF_ENTITY_ID, CONF_ID from .. import homeassistant_ns -DEPENDENCIES = ['api'] -HomeassistantBinarySensor = homeassistant_ns.class_('HomeassistantBinarySensor', - binary_sensor.BinarySensor, - cg.Component) +DEPENDENCIES = ["api"] +HomeassistantBinarySensor = homeassistant_ns.class_( + "HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component +) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(HomeassistantBinarySensor), - cv.Required(CONF_ENTITY_ID): cv.entity_id, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HomeassistantBinarySensor), + cv.Required(CONF_ENTITY_ID): cv.entity_id, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/homeassistant/sensor/__init__.py b/esphome/components/homeassistant/sensor/__init__.py index 577efca79b87..9c19c7867fbf 100644 --- a/esphome/components/homeassistant/sensor/__init__.py +++ b/esphome/components/homeassistant/sensor/__init__.py @@ -1,18 +1,29 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import CONF_ENTITY_ID, CONF_ID, ICON_EMPTY, UNIT_EMPTY +from esphome.const import ( + CONF_ENTITY_ID, + CONF_ID, + ICON_EMPTY, + UNIT_EMPTY, + DEVICE_CLASS_EMPTY, +) from .. import homeassistant_ns -DEPENDENCIES = ['api'] +DEPENDENCIES = ["api"] -HomeassistantSensor = homeassistant_ns.class_('HomeassistantSensor', sensor.Sensor, - cg.Component) +HomeassistantSensor = homeassistant_ns.class_( + "HomeassistantSensor", sensor.Sensor, cg.Component +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1).extend({ - cv.GenerateID(): cv.declare_id(HomeassistantSensor), - cv.Required(CONF_ENTITY_ID): cv.entity_id, -}) +CONFIG_SCHEMA = sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY +).extend( + { + cv.GenerateID(): cv.declare_id(HomeassistantSensor), + cv.Required(CONF_ENTITY_ID): cv.entity_id, + } +) def to_code(config): diff --git a/esphome/components/homeassistant/text_sensor/__init__.py b/esphome/components/homeassistant/text_sensor/__init__.py index 2d06473f3ce8..478bd1c3d20a 100644 --- a/esphome/components/homeassistant/text_sensor/__init__.py +++ b/esphome/components/homeassistant/text_sensor/__init__.py @@ -4,15 +4,18 @@ from esphome.const import CONF_ENTITY_ID, CONF_ID from .. import homeassistant_ns -DEPENDENCIES = ['api'] +DEPENDENCIES = ["api"] -HomeassistantTextSensor = homeassistant_ns.class_('HomeassistantTextSensor', - text_sensor.TextSensor, cg.Component) +HomeassistantTextSensor = homeassistant_ns.class_( + "HomeassistantTextSensor", text_sensor.TextSensor, cg.Component +) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(HomeassistantTextSensor), - cv.Required(CONF_ENTITY_ID): cv.entity_id, -}) +CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HomeassistantTextSensor), + cv.Required(CONF_ENTITY_ID): cv.entity_id, + } +) def to_code(config): diff --git a/esphome/components/homeassistant/time/__init__.py b/esphome/components/homeassistant/time/__init__.py index fd40de68d905..e995421302c2 100644 --- a/esphome/components/homeassistant/time/__init__.py +++ b/esphome/components/homeassistant/time/__init__.py @@ -4,17 +4,19 @@ from esphome.const import CONF_ID from .. import homeassistant_ns -DEPENDENCIES = ['api'] +DEPENDENCIES = ["api"] -HomeassistantTime = homeassistant_ns.class_('HomeassistantTime', time_.RealTimeClock) +HomeassistantTime = homeassistant_ns.class_("HomeassistantTime", time_.RealTimeClock) -CONFIG_SCHEMA = time_.TIME_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(HomeassistantTime), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = time_.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HomeassistantTime), + } +).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) - cg.add_define('USE_HOMEASSISTANT_TIME') + cg.add_define("USE_HOMEASSISTANT_TIME") diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index f7ec3cdbbf10..dee3fe8f774c 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -3,39 +3,59 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ID, CONF_TIMEOUT, CONF_ESPHOME, CONF_METHOD, \ - CONF_ARDUINO_VERSION, ARDUINO_VERSION_ESP8266, CONF_URL +from esphome.const import ( + CONF_ID, + CONF_TIMEOUT, + CONF_ESPHOME, + CONF_METHOD, + CONF_ARDUINO_VERSION, + ARDUINO_VERSION_ESP8266, + CONF_TRIGGER_ID, + CONF_URL, +) from esphome.core import CORE, Lambda from esphome.core_config import PLATFORMIO_ESP8266_LUT -DEPENDENCIES = ['network'] -AUTO_LOAD = ['json'] +DEPENDENCIES = ["network"] +AUTO_LOAD = ["json"] -http_request_ns = cg.esphome_ns.namespace('http_request') -HttpRequestComponent = http_request_ns.class_('HttpRequestComponent', cg.Component) -HttpRequestSendAction = http_request_ns.class_('HttpRequestSendAction', automation.Action) +http_request_ns = cg.esphome_ns.namespace("http_request") +HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component) +HttpRequestSendAction = http_request_ns.class_( + "HttpRequestSendAction", automation.Action +) +HttpRequestResponseTrigger = http_request_ns.class_( + "HttpRequestResponseTrigger", automation.Trigger +) -CONF_HEADERS = 'headers' -CONF_USERAGENT = 'useragent' -CONF_BODY = 'body' -CONF_JSON = 'json' -CONF_VERIFY_SSL = 'verify_ssl' +CONF_HEADERS = "headers" +CONF_USERAGENT = "useragent" +CONF_BODY = "body" +CONF_JSON = "json" +CONF_VERIFY_SSL = "verify_ssl" +CONF_ON_RESPONSE = "on_response" def validate_framework(config): if CORE.is_esp32: return config - version = 'RECOMMENDED' + version = "RECOMMENDED" if CONF_ARDUINO_VERSION in CORE.raw_config[CONF_ESPHOME]: version = CORE.raw_config[CONF_ESPHOME][CONF_ARDUINO_VERSION] - if version in ['LATEST', 'DEV']: + if version in ["LATEST", "DEV"]: return config - framework = PLATFORMIO_ESP8266_LUT[version] if version in PLATFORMIO_ESP8266_LUT else version - if framework < ARDUINO_VERSION_ESP8266['2.5.1']: - raise cv.Invalid('This component is not supported on arduino framework version below 2.5.1') + framework = ( + PLATFORMIO_ESP8266_LUT[version] + if version in PLATFORMIO_ESP8266_LUT + else version + ) + if framework < ARDUINO_VERSION_ESP8266["2.5.1"]: + raise cv.Invalid( + "This component is not supported on arduino framework version below 2.5.1" + ) return config @@ -44,34 +64,47 @@ def validate_url(value): try: parsed = list(urlparse.urlparse(value)) except Exception as err: - raise cv.Invalid('Invalid URL') from err + raise cv.Invalid("Invalid URL") from err if not parsed[0] or not parsed[1]: - raise cv.Invalid('URL must have a URL scheme and host') + raise cv.Invalid("URL must have a URL scheme and host") - if parsed[0] not in ['http', 'https']: - raise cv.Invalid('Scheme must be http or https') + if parsed[0] not in ["http", "https"]: + raise cv.Invalid("Scheme must be http or https") if not parsed[2]: - parsed[2] = '/' + parsed[2] = "/" return urlparse.urlunparse(parsed) def validate_secure_url(config): url_ = config[CONF_URL] - if config.get(CONF_VERIFY_SSL) and not isinstance(url_, Lambda) \ - and url_.lower().startswith('https:'): - raise cv.Invalid('Currently ESPHome doesn\'t support SSL verification. ' - 'Set \'verify_ssl: false\' to make insecure HTTPS requests.') + if ( + config.get(CONF_VERIFY_SSL) + and not isinstance(url_, Lambda) + and url_.lower().startswith("https:") + ): + raise cv.Invalid( + "Currently ESPHome doesn't support SSL verification. " + "Set 'verify_ssl: false' to make insecure HTTPS requests." + ) return config -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(HttpRequestComponent), - cv.Optional(CONF_USERAGENT, 'ESPHome'): cv.string, - cv.Optional(CONF_TIMEOUT, default='5s'): cv.positive_time_period_milliseconds, -}).add_extra(validate_framework).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HttpRequestComponent), + cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, + cv.Optional( + CONF_TIMEOUT, default="5s" + ): cv.positive_time_period_milliseconds, + } + ) + .add_extra(validate_framework) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): @@ -81,41 +114,63 @@ def to_code(config): yield cg.register_component(var, config) -HTTP_REQUEST_ACTION_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.use_id(HttpRequestComponent), - cv.Required(CONF_URL): cv.templatable(validate_url), - cv.Optional(CONF_HEADERS): cv.All(cv.Schema({cv.string: cv.templatable(cv.string)})), - cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, -}).add_extra(validate_secure_url) +HTTP_REQUEST_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(HttpRequestComponent), + cv.Required(CONF_URL): cv.templatable(validate_url), + cv.Optional(CONF_HEADERS): cv.All( + cv.Schema({cv.string: cv.templatable(cv.string)}) + ), + cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + cv.Optional(CONF_ON_RESPONSE): automation.validate_automation( + {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)} + ), + } +).add_extra(validate_secure_url) HTTP_REQUEST_GET_ACTION_SCHEMA = automation.maybe_conf( - CONF_URL, HTTP_REQUEST_ACTION_SCHEMA.extend({ - cv.Optional(CONF_METHOD, default='GET'): cv.one_of('GET', upper=True), - }) + CONF_URL, + HTTP_REQUEST_ACTION_SCHEMA.extend( + { + cv.Optional(CONF_METHOD, default="GET"): cv.one_of("GET", upper=True), + } + ), ) HTTP_REQUEST_POST_ACTION_SCHEMA = automation.maybe_conf( - CONF_URL, HTTP_REQUEST_ACTION_SCHEMA.extend({ - cv.Optional(CONF_METHOD, default='POST'): cv.one_of('POST', upper=True), - cv.Exclusive(CONF_BODY, 'body'): cv.templatable(cv.string), - cv.Exclusive(CONF_JSON, 'body'): cv.Any( - cv.lambda_, cv.All(cv.Schema({cv.string: cv.templatable(cv.string_strict)})), + CONF_URL, + HTTP_REQUEST_ACTION_SCHEMA.extend( + { + cv.Optional(CONF_METHOD, default="POST"): cv.one_of("POST", upper=True), + cv.Exclusive(CONF_BODY, "body"): cv.templatable(cv.string), + cv.Exclusive(CONF_JSON, "body"): cv.Any( + cv.lambda_, + cv.All(cv.Schema({cv.string: cv.templatable(cv.string_strict)})), + ), + } + ), +) +HTTP_REQUEST_SEND_ACTION_SCHEMA = HTTP_REQUEST_ACTION_SCHEMA.extend( + { + cv.Required(CONF_METHOD): cv.one_of( + "GET", "POST", "PUT", "DELETE", "PATCH", upper=True + ), + cv.Exclusive(CONF_BODY, "body"): cv.templatable(cv.string), + cv.Exclusive(CONF_JSON, "body"): cv.Any( + cv.lambda_, + cv.All(cv.Schema({cv.string: cv.templatable(cv.string_strict)})), ), - }) + } ) -HTTP_REQUEST_SEND_ACTION_SCHEMA = HTTP_REQUEST_ACTION_SCHEMA.extend({ - cv.Required(CONF_METHOD): cv.one_of('GET', 'POST', 'PUT', 'DELETE', 'PATCH', upper=True), - cv.Exclusive(CONF_BODY, 'body'): cv.templatable(cv.string), - cv.Exclusive(CONF_JSON, 'body'): cv.Any( - cv.lambda_, cv.All(cv.Schema({cv.string: cv.templatable(cv.string_strict)})), - ), -}) -@automation.register_action('http_request.get', HttpRequestSendAction, - HTTP_REQUEST_GET_ACTION_SCHEMA) -@automation.register_action('http_request.post', HttpRequestSendAction, - HTTP_REQUEST_POST_ACTION_SCHEMA) -@automation.register_action('http_request.send', HttpRequestSendAction, - HTTP_REQUEST_SEND_ACTION_SCHEMA) +@automation.register_action( + "http_request.get", HttpRequestSendAction, HTTP_REQUEST_GET_ACTION_SCHEMA +) +@automation.register_action( + "http_request.post", HttpRequestSendAction, HTTP_REQUEST_POST_ACTION_SCHEMA +) +@automation.register_action( + "http_request.send", HttpRequestSendAction, HTTP_REQUEST_SEND_ACTION_SCHEMA +) def http_request_action_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -129,7 +184,7 @@ def http_request_action_to_code(config, action_id, template_arg, args): if CONF_JSON in config: json_ = config[CONF_JSON] if isinstance(json_, Lambda): - args_ = args + [(cg.JsonObjectRef, 'root')] + args_ = args + [(cg.JsonObjectRef, "root")] lambda_ = yield cg.process_lambda(json_, args_, return_type=cg.void) cg.add(var.set_json(lambda_)) else: @@ -137,7 +192,14 @@ def http_request_action_to_code(config, action_id, template_arg, args): template_ = yield cg.templatable(json_[key], args, cg.std_string) cg.add(var.add_json(key, template_)) for key in config.get(CONF_HEADERS, []): - template_ = yield cg.templatable(config[CONF_HEADERS][key], args, cg.const_char_ptr) + template_ = yield cg.templatable( + config[CONF_HEADERS][key], args, cg.const_char_ptr + ) cg.add(var.add_header(key, template_)) + for conf in config.get(CONF_ON_RESPONSE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_response_trigger(trigger)) + yield automation.build_automation(trigger, [(int, "status_code")], conf) + yield var diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 0e73a7956a66..79f698441e4c 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -24,7 +24,7 @@ void HttpRequestComponent::set_url(std::string url) { this->client_.setReuse(true); } -void HttpRequestComponent::send() { +void HttpRequestComponent::send(const std::vector &response_triggers) { bool begin_status = false; const String url = this->url_.c_str(); #ifdef ARDUINO_ARCH_ESP32 @@ -54,6 +54,9 @@ void HttpRequestComponent::send() { } int http_code = this->client_.sendRequest(this->method_, this->body_.c_str()); + for (auto *trigger : response_triggers) + trigger->process(http_code); + if (http_code < 0) { ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", this->url_.c_str(), HTTPClient::errorToString(http_code).c_str()); diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index c69683db0e9a..f2c688d2f945 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -22,6 +22,8 @@ struct Header { const char *value; }; +class HttpRequestResponseTrigger; + class HttpRequestComponent : public Component { public: void dump_config() override; @@ -33,7 +35,7 @@ class HttpRequestComponent : public Component { void set_timeout(uint16_t timeout) { this->timeout_ = timeout; } void set_body(std::string body) { this->body_ = body; } void set_headers(std::list
headers) { this->headers_ = headers; } - void send(); + void send(const std::vector &response_triggers); void close(); const char *get_string(); @@ -69,6 +71,8 @@ template class HttpRequestSendAction : public Action { void set_json(std::function json_func) { this->json_func_ = json_func; } + void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); } + void play(Ts... x) override { this->parent_->set_url(this->url_.value(x...)); this->parent_->set_method(this->method_.value(x...)); @@ -100,7 +104,7 @@ template class HttpRequestSendAction : public Action { } this->parent_->set_headers(headers); } - this->parent_->send(); + this->parent_->send(this->response_triggers_); this->parent_->close(); } @@ -116,6 +120,12 @@ template class HttpRequestSendAction : public Action { std::map> headers_{}; std::map> json_{}; std::function json_func_{nullptr}; + std::vector response_triggers_; +}; + +class HttpRequestResponseTrigger : public Trigger { + public: + void process(int status_code) { this->trigger(status_code); } }; } // namespace http_request diff --git a/esphome/components/htu21d/sensor.py b/esphome/components/htu21d/sensor.py index 20053d27ddac..258681a5aa06 100644 --- a/esphome/components/htu21d/sensor.py +++ b/esphome/components/htu21d/sensor.py @@ -1,19 +1,39 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \ - ICON_THERMOMETER, UNIT_CELSIUS, UNIT_PERCENT, ICON_WATER_PERCENT +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -htu21d_ns = cg.esphome_ns.namespace('htu21d') -HTU21DComponent = htu21d_ns.class_('HTU21DComponent', cg.PollingComponent, i2c.I2CDevice) +htu21d_ns = cg.esphome_ns.namespace("htu21d") +HTU21DComponent = htu21d_ns.class_( + "HTU21DComponent", cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(HTU21DComponent), - cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Required(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HTU21DComponent), + cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Required(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)) +) def to_code(config): diff --git a/esphome/components/hx711/sensor.py b/esphome/components/hx711/sensor.py index 2fc333a243ba..191a7386e6ce 100644 --- a/esphome/components/hx711/sensor.py +++ b/esphome/components/hx711/sensor.py @@ -2,26 +2,39 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor -from esphome.const import CONF_CLK_PIN, CONF_GAIN, CONF_ID, ICON_SCALE +from esphome.const import ( + CONF_CLK_PIN, + CONF_GAIN, + CONF_ID, + DEVICE_CLASS_EMPTY, + ICON_SCALE, + UNIT_EMPTY, +) -hx711_ns = cg.esphome_ns.namespace('hx711') -HX711Sensor = hx711_ns.class_('HX711Sensor', sensor.Sensor, cg.PollingComponent) +hx711_ns = cg.esphome_ns.namespace("hx711") +HX711Sensor = hx711_ns.class_("HX711Sensor", sensor.Sensor, cg.PollingComponent) -CONF_DOUT_PIN = 'dout_pin' +CONF_DOUT_PIN = "dout_pin" -HX711Gain = hx711_ns.enum('HX711Gain') +HX711Gain = hx711_ns.enum("HX711Gain") GAINS = { 128: HX711Gain.HX711_GAIN_128, 32: HX711Gain.HX711_GAIN_32, 64: HX711Gain.HX711_GAIN_64, } -CONFIG_SCHEMA = sensor.sensor_schema('', ICON_SCALE, 0).extend({ - cv.GenerateID(): cv.declare_id(HX711Sensor), - cv.Required(CONF_DOUT_PIN): pins.gpio_input_pin_schema, - cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_GAIN, default=128): cv.enum(GAINS, int=True), -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_EMPTY, ICON_SCALE, 0, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(HX711Sensor), + cv.Required(CONF_DOUT_PIN): pins.gpio_input_pin_schema, + cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_GAIN, default=128): cv.enum(GAINS, int=True), + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 91c6a9719098..144683590471 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -1,24 +1,34 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_SCAN, CONF_SCL, CONF_SDA, CONF_ADDRESS, \ - CONF_I2C_ID +from esphome.const import ( + CONF_FREQUENCY, + CONF_ID, + CONF_SCAN, + CONF_SCL, + CONF_SDA, + CONF_ADDRESS, + CONF_I2C_ID, +) from esphome.core import coroutine, coroutine_with_priority -CODEOWNERS = ['@esphome/core'] -i2c_ns = cg.esphome_ns.namespace('i2c') -I2CComponent = i2c_ns.class_('I2CComponent', cg.Component) -I2CDevice = i2c_ns.class_('I2CDevice') +CODEOWNERS = ["@esphome/core"] +i2c_ns = cg.esphome_ns.namespace("i2c") +I2CComponent = i2c_ns.class_("I2CComponent", cg.Component) +I2CDevice = i2c_ns.class_("I2CDevice") MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(I2CComponent), - cv.Optional(CONF_SDA, default='SDA'): pins.input_pin, - cv.Optional(CONF_SCL, default='SCL'): pins.input_pin, - cv.Optional(CONF_FREQUENCY, default='50kHz'): - cv.All(cv.frequency, cv.Range(min=0, min_included=False)), - cv.Optional(CONF_SCAN, default=True): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(I2CComponent), + cv.Optional(CONF_SDA, default="SDA"): pins.input_pin, + cv.Optional(CONF_SCL, default="SCL"): pins.input_pin, + cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All( + cv.frequency, cv.Range(min=0, min_included=False) + ), + cv.Optional(CONF_SCAN, default=True): cv.boolean, + } +).extend(cv.COMPONENT_SCHEMA) @coroutine_with_priority(1.0) @@ -31,7 +41,7 @@ def to_code(config): cg.add(var.set_scl_pin(config[CONF_SCL])) cg.add(var.set_frequency(int(config[CONF_FREQUENCY]))) cg.add(var.set_scan(config[CONF_SCAN])) - cg.add_library('Wire', None) + cg.add_library("Wire", None) def i2c_device_schema(default_address): diff --git a/esphome/components/ili9341/display.py b/esphome/components/ili9341/display.py index 0a3e9e16cc7b..8c1a28ea5d41 100644 --- a/esphome/components/ili9341/display.py +++ b/esphome/components/ili9341/display.py @@ -2,42 +2,55 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display, spi -from esphome.const import CONF_DC_PIN, \ - CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_PAGES, CONF_RESET_PIN +from esphome.const import ( + CONF_DC_PIN, + CONF_ID, + CONF_LAMBDA, + CONF_MODEL, + CONF_PAGES, + CONF_RESET_PIN, +) -DEPENDENCIES = ['spi'] +DEPENDENCIES = ["spi"] -CONF_LED_PIN = 'led_pin' +CONF_LED_PIN = "led_pin" -ili9341_ns = cg.esphome_ns.namespace('ili9341') -ili9341 = ili9341_ns.class_('ILI9341Display', cg.PollingComponent, spi.SPIDevice, - display.DisplayBuffer) -ILI9341M5Stack = ili9341_ns.class_('ILI9341M5Stack', ili9341) -ILI9341TFT24 = ili9341_ns.class_('ILI9341TFT24', ili9341) +ili9341_ns = cg.esphome_ns.namespace("ili9341") +ili9341 = ili9341_ns.class_( + "ILI9341Display", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer +) +ILI9341M5Stack = ili9341_ns.class_("ILI9341M5Stack", ili9341) +ILI9341TFT24 = ili9341_ns.class_("ILI9341TFT24", ili9341) -ILI9341Model = ili9341_ns.enum('ILI9341Model') +ILI9341Model = ili9341_ns.enum("ILI9341Model") MODELS = { - 'M5STACK': ILI9341Model.M5STACK, - 'TFT_2.4': ILI9341Model.TFT_24, + "M5STACK": ILI9341Model.M5STACK, + "TFT_2.4": ILI9341Model.TFT_24, } ILI9341_MODEL = cv.enum(MODELS, upper=True, space="_") -CONFIG_SCHEMA = cv.All(display.FULL_DISPLAY_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(ili9341), - cv.Required(CONF_MODEL): ILI9341_MODEL, - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_LED_PIN): pins.gpio_output_pin_schema, -}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema()), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ili9341), + cv.Required(CONF_MODEL): ILI9341_MODEL, + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_LED_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(cv.polling_component_schema("1s")) + .extend(spi.spi_device_schema()), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): - if config[CONF_MODEL] == 'M5STACK': + if config[CONF_MODEL] == "M5STACK": lcd_type = ILI9341M5Stack - if config[CONF_MODEL] == 'TFT_2.4': + if config[CONF_MODEL] == "TFT_2.4": lcd_type = ILI9341TFT24 rhs = lcd_type.new() var = cg.Pvariable(config[CONF_ID], rhs) @@ -50,8 +63,9 @@ def to_code(config): cg.add(var.set_dc_pin(dc)) if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) if CONF_RESET_PIN in config: reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN]) diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index c0e787328428..48a95f4df0b0 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -130,7 +130,7 @@ uint8_t ILI9341Display::convert_to_8bit_color_(uint16_t color_16bit) { } void ILI9341Display::fill(Color color) { - auto color565 = color.to_rgb_565(); + auto color565 = display::ColorUtil::color_to_565(color); memset(this->buffer_, convert_to_8bit_color_(color565), this->get_buffer_length_()); this->x_low_ = 0; this->y_low_ = 0; @@ -142,7 +142,7 @@ void ILI9341Display::fill_internal_(Color color) { this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal()); this->start_data_(); - auto color565 = color.to_rgb_565(); + auto color565 = display::ColorUtil::color_to_565(color); for (uint32_t i = 0; i < (this->get_width_internal()) * (this->get_height_internal()); i++) { this->write_byte(color565 >> 8); this->write_byte(color565); @@ -162,7 +162,7 @@ void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) this->y_high_ = (y > this->y_high_) ? y : this->y_high_; uint32_t pos = (y * width_) + x; - auto color565 = color.to_rgb_565(); + auto color565 = display::ColorUtil::color_to_565(color); buffer_[pos] = convert_to_8bit_color_(color565); } diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index 3558d9660e11..b6297796908f 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -9,28 +9,32 @@ _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['display'] +DEPENDENCIES = ["display"] MULTI_CONF = True -ImageType = display.display_ns.enum('ImageType') +ImageType = display.display_ns.enum("ImageType") IMAGE_TYPE = { - 'BINARY': ImageType.IMAGE_TYPE_BINARY, - 'GRAYSCALE': ImageType.IMAGE_TYPE_GRAYSCALE, - 'RGB24': ImageType.IMAGE_TYPE_RGB24, + "BINARY": ImageType.IMAGE_TYPE_BINARY, + "GRAYSCALE": ImageType.IMAGE_TYPE_GRAYSCALE, + "RGB24": ImageType.IMAGE_TYPE_RGB24, } -Image_ = display.display_ns.class_('Image') +Image_ = display.display_ns.class_("Image") -CONF_RAW_DATA_ID = 'raw_data_id' +CONF_RAW_DATA_ID = "raw_data_id" -IMAGE_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(Image_), - cv.Required(CONF_FILE): cv.file_, - cv.Optional(CONF_RESIZE): cv.dimensions, - cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(IMAGE_TYPE, upper=True), - cv.Optional(CONF_DITHER, default='NONE'): cv.one_of("NONE", "FLOYDSTEINBERG", upper=True), - cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), -}) +IMAGE_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(Image_), + cv.Required(CONF_FILE): cv.file_, + cv.Optional(CONF_RESIZE): cv.dimensions, + cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(IMAGE_TYPE, upper=True), + cv.Optional(CONF_DITHER, default="NONE"): cv.one_of( + "NONE", "FLOYDSTEINBERG", upper=True + ), + cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), + } +) CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA) @@ -51,12 +55,14 @@ def to_code(config): width, height = image.size else: if width > 500 or height > 500: - _LOGGER.warning("The image you requested is very big. Please consider using" - " the resize parameter.") - - dither = Image.NONE if config[CONF_DITHER] == 'NONE' else Image.FLOYDSTEINBERG - if config[CONF_TYPE] == 'GRAYSCALE': - image = image.convert('L', dither=dither) + _LOGGER.warning( + "The image you requested is very big. Please consider using" + " the resize parameter." + ) + + dither = Image.NONE if config[CONF_DITHER] == "NONE" else Image.FLOYDSTEINBERG + if config[CONF_TYPE] == "GRAYSCALE": + image = image.convert("L", dither=dither) pixels = list(image.getdata()) data = [0 for _ in range(height * width)] pos = 0 @@ -64,8 +70,8 @@ def to_code(config): data[pos] = pix pos += 1 - elif config[CONF_TYPE] == 'RGB24': - image = image.convert('RGB') + elif config[CONF_TYPE] == "RGB24": + image = image.convert("RGB") pixels = list(image.getdata()) data = [0 for _ in range(height * width * 3)] pos = 0 @@ -77,8 +83,8 @@ def to_code(config): data[pos] = pix[2] pos += 1 - elif config[CONF_TYPE] == 'BINARY': - image = image.convert('1', dither=dither) + elif config[CONF_TYPE] == "BINARY": + image = image.convert("1", dither=dither) width8 = ((width + 7) // 8) * 8 data = [0 for _ in range(height * width8 // 8)] for y in range(height): @@ -90,5 +96,6 @@ def to_code(config): rhs = [HexInt(x) for x in data] prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) - cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, - IMAGE_TYPE[config[CONF_TYPE]]) + cg.new_Pvariable( + config[CONF_ID], prog_arr, width, height, IMAGE_TYPE[config[CONF_TYPE]] + ) diff --git a/esphome/components/ina219/sensor.py b/esphome/components/ina219/sensor.py index 6a61e16226d3..d122754b8860 100644 --- a/esphome/components/ina219/sensor.py +++ b/esphome/components/ina219/sensor.py @@ -1,26 +1,61 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_BUS_VOLTAGE, CONF_CURRENT, CONF_ID, \ - CONF_MAX_CURRENT, CONF_MAX_VOLTAGE, CONF_POWER, CONF_SHUNT_RESISTANCE, \ - CONF_SHUNT_VOLTAGE, ICON_FLASH, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT - -DEPENDENCIES = ['i2c'] - -ina219_ns = cg.esphome_ns.namespace('ina219') -INA219Component = ina219_ns.class_('INA219Component', cg.PollingComponent, i2c.I2CDevice) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(INA219Component), - cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), - cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), - cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 3), - cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2), - cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All(cv.resistance, - cv.Range(min=0.0, max=32.0)), - cv.Optional(CONF_MAX_VOLTAGE, default=32.0): cv.All(cv.voltage, cv.Range(min=0.0, max=32.0)), - cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All(cv.current, cv.Range(min=0.0)), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40)) +from esphome.const import ( + CONF_BUS_VOLTAGE, + CONF_CURRENT, + CONF_ID, + CONF_MAX_CURRENT, + CONF_MAX_VOLTAGE, + CONF_POWER, + CONF_SHUNT_RESISTANCE, + CONF_SHUNT_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, +) + +DEPENDENCIES = ["i2c"] + +ina219_ns = cg.esphome_ns.namespace("ina219") +INA219Component = ina219_ns.class_( + "INA219Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(INA219Component), + cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 3, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER + ), + cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All( + cv.resistance, cv.Range(min=0.0, max=32.0) + ), + cv.Optional(CONF_MAX_VOLTAGE, default=32.0): cv.All( + cv.voltage, cv.Range(min=0.0, max=32.0) + ), + cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All( + cv.current, cv.Range(min=0.0) + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)) +) def to_code(config): diff --git a/esphome/components/ina226/sensor.py b/esphome/components/ina226/sensor.py index 066363b3d4a1..2b7346fbe203 100644 --- a/esphome/components/ina226/sensor.py +++ b/esphome/components/ina226/sensor.py @@ -1,24 +1,57 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_BUS_VOLTAGE, CONF_CURRENT, CONF_ID, \ - CONF_MAX_CURRENT, CONF_POWER, CONF_SHUNT_RESISTANCE, \ - CONF_SHUNT_VOLTAGE, ICON_FLASH, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT - -DEPENDENCIES = ['i2c'] - -ina226_ns = cg.esphome_ns.namespace('ina226') -INA226Component = ina226_ns.class_('INA226Component', cg.PollingComponent, i2c.I2CDevice) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(INA226Component), - cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), - cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), - cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 3), - cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2), - cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All(cv.resistance, cv.Range(min=0.0)), - cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All(cv.current, cv.Range(min=0.0)), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40)) +from esphome.const import ( + CONF_BUS_VOLTAGE, + CONF_CURRENT, + CONF_ID, + CONF_MAX_CURRENT, + CONF_POWER, + CONF_SHUNT_RESISTANCE, + CONF_SHUNT_VOLTAGE, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + ICON_EMPTY, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, +) + +DEPENDENCIES = ["i2c"] + +ina226_ns = cg.esphome_ns.namespace("ina226") +INA226Component = ina226_ns.class_( + "INA226Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(INA226Component), + cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 3, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER + ), + cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All( + cv.resistance, cv.Range(min=0.0) + ), + cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All( + cv.current, cv.Range(min=0.0) + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)) +) def to_code(config): diff --git a/esphome/components/ina3221/sensor.py b/esphome/components/ina3221/sensor.py index 1c26533cc498..c055e149b700 100644 --- a/esphome/components/ina3221/sensor.py +++ b/esphome/components/ina3221/sensor.py @@ -1,34 +1,65 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_BUS_VOLTAGE, CONF_CURRENT, CONF_ID, CONF_POWER, \ - CONF_SHUNT_RESISTANCE, CONF_SHUNT_VOLTAGE, ICON_FLASH, \ - UNIT_VOLT, UNIT_AMPERE, UNIT_WATT - -DEPENDENCIES = ['i2c'] - -CONF_CHANNEL_1 = 'channel_1' -CONF_CHANNEL_2 = 'channel_2' -CONF_CHANNEL_3 = 'channel_3' - -ina3221_ns = cg.esphome_ns.namespace('ina3221') -INA3221Component = ina3221_ns.class_('INA3221Component', cg.PollingComponent, i2c.I2CDevice) - -INA3221_CHANNEL_SCHEMA = cv.Schema({ - cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), - cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), - cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), - cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2), - cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All(cv.resistance, - cv.Range(min=0.0, max=32.0)), -}) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(INA3221Component), - cv.Optional(CONF_CHANNEL_1): INA3221_CHANNEL_SCHEMA, - cv.Optional(CONF_CHANNEL_2): INA3221_CHANNEL_SCHEMA, - cv.Optional(CONF_CHANNEL_3): INA3221_CHANNEL_SCHEMA, -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40)) +from esphome.const import ( + CONF_BUS_VOLTAGE, + CONF_CURRENT, + CONF_ID, + CONF_POWER, + CONF_SHUNT_RESISTANCE, + CONF_SHUNT_VOLTAGE, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + ICON_EMPTY, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, +) + +DEPENDENCIES = ["i2c"] + +CONF_CHANNEL_1 = "channel_1" +CONF_CHANNEL_2 = "channel_2" +CONF_CHANNEL_3 = "channel_3" + +ina3221_ns = cg.esphome_ns.namespace("ina3221") +INA3221Component = ina3221_ns.class_( + "INA3221Component", cg.PollingComponent, i2c.I2CDevice +) + +INA3221_CHANNEL_SCHEMA = cv.Schema( + { + cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER + ), + cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All( + cv.resistance, cv.Range(min=0.0, max=32.0) + ), + } +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(INA3221Component), + cv.Optional(CONF_CHANNEL_1): INA3221_CHANNEL_SCHEMA, + cv.Optional(CONF_CHANNEL_2): INA3221_CHANNEL_SCHEMA, + cv.Optional(CONF_CHANNEL_3): INA3221_CHANNEL_SCHEMA, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)) +) def to_code(config): diff --git a/esphome/components/inkbird_ibsth1_mini/__init__.py b/esphome/components/inkbird_ibsth1_mini/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp new file mode 100644 index 000000000000..a1c1c5d2010f --- /dev/null +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -0,0 +1,86 @@ +#include "inkbird_ibsth1_mini.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace inkbird_ibsth1_mini { + +static const char *TAG = "inkbird_ibsth1_mini"; + +void InkbirdIBSTH1_MINI::dump_config() { + ESP_LOGCONFIG(TAG, "Inkbird IBS TH1 MINI"); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); +} + +bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + // The below is based on my research and reverse engineering of a single device + // It is entirely possible that some of that may be inaccurate or incomplete + + // for Inkbird IBS-TH1 Mini device we expect + // 1) expected mac address + // 2) device address type == PUBLIC + // 3) no service datas + // 4) one manufacturer datas + // 5) the manufacturer datas should contain a 16-bit uuid amd a 7-byte data vector + // 6) the 7-byte data component should have data[2] == 0 and data[6] == 8 + + // the address should match the address we declared + if (device.address_uint64() != this->address_) { + ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); + return false; + } + if (device.get_address_type() != BLE_ADDR_TYPE_PUBLIC) { + ESP_LOGVV(TAG, "parse_device(): address is not public"); + return false; + } + if (device.get_service_datas().size() != 0) { + ESP_LOGVV(TAG, "parse_device(): service_data is expected to be empty"); + return false; + } + auto mnfDatas = device.get_manufacturer_datas(); + if (mnfDatas.size() != 1) { + ESP_LOGVV(TAG, "parse_device(): manufacturer_datas is expected to have a single element"); + return false; + } + auto mnfData = mnfDatas[0]; + if (mnfData.uuid.get_uuid().len != ESP_UUID_LEN_16) { + ESP_LOGVV(TAG, "parse_device(): manufacturer data element is expected to have uuid of length 16"); + return false; + } + if (mnfData.data.size() != 7) { + ESP_LOGVV(TAG, "parse_device(): manufacturer data element length is expected to be of length 7"); + return false; + } + if ((mnfData.data[2] != 0) || (mnfData.data[6] != 8)) { + ESP_LOGVV(TAG, "parse_device(): unexpected data"); + return false; + } + + // sensor output encoding + // data[5] is a battery level + // data[0] and data[1] is humidity * 100 (in pct) + // uuid is a temperature * 100 (in Celcius) + auto battery_level = mnfData.data[5]; + auto temperature = mnfData.uuid.get_uuid().uuid.uuid16 / 100.0f; + auto humidity = ((mnfData.data[1] << 8) + mnfData.data[0]) / 100.0f; + + if (this->temperature_ != nullptr) { + this->temperature_->publish_state(temperature); + } + if (this->humidity_ != nullptr) { + this->humidity_->publish_state(humidity); + } + if (this->battery_level_ != nullptr) { + this->battery_level_->publish_state(battery_level); + } + + return true; +} + +} // namespace inkbird_ibsth1_mini +} // namespace esphome + +#endif diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h new file mode 100644 index 000000000000..38e72dad1750 --- /dev/null +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace inkbird_ibsth1_mini { + +class InkbirdIBSTH1_MINI : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { address_ = address; } + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } + void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } + + protected: + uint64_t address_; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; +}; + +} // namespace inkbird_ibsth1_mini +} // namespace esphome + +#endif diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py new file mode 100644 index 000000000000..c67acb8595cb --- /dev/null +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -0,0 +1,62 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_HUMIDITY, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, + CONF_ID, +) + +CODEOWNERS = ["@fkirill"] +DEPENDENCIES = ["esp32_ble_tracker"] + +inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace("inkbird_ibsth1_mini") +InkbirdUBSTH1_MINI = inkbird_ibsth1_mini_ns.class_( + "InkbirdIBSTH1_MINI", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(InkbirdUBSTH1_MINI), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield esp32_ble_tracker.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + + if CONF_TEMPERATURE in config: + sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_HUMIDITY in config: + sens = yield sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity(sens)) + if CONF_BATTERY_LEVEL in config: + sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(var.set_battery_level(sens)) diff --git a/esphome/components/inkplate6/__init__.py b/esphome/components/inkplate6/__init__.py index ba7653988b61..b1de57df8f1c 100644 --- a/esphome/components/inkplate6/__init__.py +++ b/esphome/components/inkplate6/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@jesserockz'] +CODEOWNERS = ["@jesserockz"] diff --git a/esphome/components/inkplate6/display.py b/esphome/components/inkplate6/display.py index 4f35901bd468..323936d2c42d 100644 --- a/esphome/components/inkplate6/display.py +++ b/esphome/components/inkplate6/display.py @@ -2,67 +2,96 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display, i2c -from esphome.const import CONF_FULL_UPDATE_EVERY, CONF_ID, CONF_LAMBDA, CONF_PAGES, \ - CONF_WAKEUP_PIN, ESP_PLATFORM_ESP32 - -DEPENDENCIES = ['i2c'] +from esphome.const import ( + CONF_FULL_UPDATE_EVERY, + CONF_ID, + CONF_LAMBDA, + CONF_PAGES, + CONF_WAKEUP_PIN, + ESP_PLATFORM_ESP32, +) + +DEPENDENCIES = ["i2c"] ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -CONF_DISPLAY_DATA_0_PIN = 'display_data_0_pin' -CONF_DISPLAY_DATA_1_PIN = 'display_data_1_pin' -CONF_DISPLAY_DATA_2_PIN = 'display_data_2_pin' -CONF_DISPLAY_DATA_3_PIN = 'display_data_3_pin' -CONF_DISPLAY_DATA_4_PIN = 'display_data_4_pin' -CONF_DISPLAY_DATA_5_PIN = 'display_data_5_pin' -CONF_DISPLAY_DATA_6_PIN = 'display_data_6_pin' -CONF_DISPLAY_DATA_7_PIN = 'display_data_7_pin' - -CONF_CL_PIN = 'cl_pin' -CONF_CKV_PIN = 'ckv_pin' -CONF_GREYSCALE = 'greyscale' -CONF_GMOD_PIN = 'gmod_pin' -CONF_GPIO0_ENABLE_PIN = 'gpio0_enable_pin' -CONF_LE_PIN = 'le_pin' -CONF_OE_PIN = 'oe_pin' -CONF_PARTIAL_UPDATING = 'partial_updating' -CONF_POWERUP_PIN = 'powerup_pin' -CONF_SPH_PIN = 'sph_pin' -CONF_SPV_PIN = 'spv_pin' -CONF_VCOM_PIN = 'vcom_pin' - - -inkplate6_ns = cg.esphome_ns.namespace('inkplate6') -Inkplate6 = inkplate6_ns.class_('Inkplate6', cg.PollingComponent, i2c.I2CDevice, - display.DisplayBuffer) - -CONFIG_SCHEMA = cv.All(display.FULL_DISPLAY_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(Inkplate6), - cv.Optional(CONF_GREYSCALE, default=False): cv.boolean, - cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean, - cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t, - # Control pins - cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_GPIO0_ENABLE_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_OE_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_POWERUP_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_SPH_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_SPV_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_VCOM_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_WAKEUP_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_CL_PIN, default=0): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_LE_PIN, default=2): pins.internal_gpio_output_pin_schema, - # Data pins - cv.Optional(CONF_DISPLAY_DATA_0_PIN, default=4): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_DISPLAY_DATA_1_PIN, default=5): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_DISPLAY_DATA_2_PIN, default=18): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_DISPLAY_DATA_3_PIN, default=19): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_DISPLAY_DATA_4_PIN, default=23): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_DISPLAY_DATA_5_PIN, default=25): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_DISPLAY_DATA_6_PIN, default=26): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_DISPLAY_DATA_7_PIN, default=27): pins.internal_gpio_output_pin_schema, -}).extend(cv.polling_component_schema('5s').extend(i2c.i2c_device_schema(0x48))), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin" +CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin" +CONF_DISPLAY_DATA_2_PIN = "display_data_2_pin" +CONF_DISPLAY_DATA_3_PIN = "display_data_3_pin" +CONF_DISPLAY_DATA_4_PIN = "display_data_4_pin" +CONF_DISPLAY_DATA_5_PIN = "display_data_5_pin" +CONF_DISPLAY_DATA_6_PIN = "display_data_6_pin" +CONF_DISPLAY_DATA_7_PIN = "display_data_7_pin" + +CONF_CL_PIN = "cl_pin" +CONF_CKV_PIN = "ckv_pin" +CONF_GREYSCALE = "greyscale" +CONF_GMOD_PIN = "gmod_pin" +CONF_GPIO0_ENABLE_PIN = "gpio0_enable_pin" +CONF_LE_PIN = "le_pin" +CONF_OE_PIN = "oe_pin" +CONF_PARTIAL_UPDATING = "partial_updating" +CONF_POWERUP_PIN = "powerup_pin" +CONF_SPH_PIN = "sph_pin" +CONF_SPV_PIN = "spv_pin" +CONF_VCOM_PIN = "vcom_pin" + + +inkplate6_ns = cg.esphome_ns.namespace("inkplate6") +Inkplate6 = inkplate6_ns.class_( + "Inkplate6", cg.PollingComponent, i2c.I2CDevice, display.DisplayBuffer +) + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Inkplate6), + cv.Optional(CONF_GREYSCALE, default=False): cv.boolean, + cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean, + cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t, + # Control pins + cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_GPIO0_ENABLE_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_OE_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_POWERUP_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_SPH_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_SPV_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_VCOM_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_WAKEUP_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_CL_PIN, default=0): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_LE_PIN, default=2): pins.internal_gpio_output_pin_schema, + # Data pins + cv.Optional( + CONF_DISPLAY_DATA_0_PIN, default=4 + ): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_DISPLAY_DATA_1_PIN, default=5 + ): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_DISPLAY_DATA_2_PIN, default=18 + ): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_DISPLAY_DATA_3_PIN, default=19 + ): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_DISPLAY_DATA_4_PIN, default=23 + ): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_DISPLAY_DATA_5_PIN, default=25 + ): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_DISPLAY_DATA_6_PIN, default=26 + ): pins.internal_gpio_output_pin_schema, + cv.Optional( + CONF_DISPLAY_DATA_7_PIN, default=27 + ): pins.internal_gpio_output_pin_schema, + } + ) + .extend(cv.polling_component_schema("5s")) + .extend(i2c.i2c_device_schema(0x48)), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): @@ -73,8 +102,9 @@ def to_code(config): yield i2c.register_i2c_device(var, config) if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) cg.add(var.set_greyscale(config[CONF_GREYSCALE])) @@ -138,4 +168,4 @@ def to_code(config): display_data_7 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_7_PIN]) cg.add(var.set_display_data_7_pin(display_data_7)) - cg.add_build_flag('-DBOARD_HAS_PSRAM') + cg.add_build_flag("-DBOARD_HAS_PSRAM") diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index 3b17ba8f52e7..d60f97c36db2 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -204,12 +204,10 @@ void Inkplate6::fill(Color color) { if (this->greyscale_) { uint8_t fill = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5; - for (uint32_t i = 0; i < this->get_buffer_length_(); i++) - this->buffer_[i] = (fill << 4) | fill; + memset(this->buffer_, (fill << 4) | fill, this->get_buffer_length_()); } else { uint8_t fill = color.is_on() ? 0x00 : 0xFF; - for (uint32_t i = 0; i < this->get_buffer_length_(); i++) - this->partial_buffer_[i] = fill; + memset(this->partial_buffer_, fill, this->get_buffer_length_()); } ESP_LOGV(TAG, "Fill finished (%lums)", millis() - start_time); @@ -233,15 +231,12 @@ void Inkplate6::display1b_() { ESP_LOGV(TAG, "Display1b called"); unsigned long start_time = millis(); - for (int i = 0; i < this->get_buffer_length_(); i++) { - this->buffer_[i] &= this->partial_buffer_[i]; - this->buffer_[i] |= this->partial_buffer_[i]; - } + memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_()); - uint16_t pos; uint32_t send; uint8_t data; uint8_t buffer_value; + const uint8_t *buffer_ptr; eink_on_(); clean_fast_(0, 1); clean_fast_(1, 5); @@ -252,75 +247,72 @@ void Inkplate6::display1b_() { clean_fast_(2, 1); clean_fast_(0, 11); + uint32_t clock = (1 << this->cl_pin_->get_pin()); ESP_LOGV(TAG, "Display1b start loops (%lums)", millis() - start_time); for (int k = 0; k < 3; k++) { - pos = this->get_buffer_length_() - 1; + buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { - buffer_value = this->buffer_[pos]; + buffer_value = *(buffer_ptr--); data = LUTB[(buffer_value >> 4) & 0x0F]; send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | (((data & B11100000) >> 5) << 25); hscan_start_(send); data = LUTB[buffer_value & 0x0F]; send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); - pos--; + (((data & B11100000) >> 5) << 25) | clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; - for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { - buffer_value = this->buffer_[pos]; + for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { + buffer_value = *(buffer_ptr--); data = LUTB[(buffer_value >> 4) & 0x0F]; send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + (((data & B11100000) >> 5) << 25) | clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; data = LUTB[buffer_value & 0x0F]; send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); - pos--; + (((data & B11100000) >> 5) << 25) | clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; } - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = send; + GPIO.out_w1tc = get_data_pin_mask_() | clock; vscan_end_(); } delayMicroseconds(230); } ESP_LOGV(TAG, "Display1b first loop x %d (%lums)", 3, millis() - start_time); - pos = this->get_buffer_length_() - 1; + buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { - buffer_value = this->buffer_[pos]; + buffer_value = *(buffer_ptr--); data = LUT2[(buffer_value >> 4) & 0x0F]; send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | (((data & B11100000) >> 5) << 25); hscan_start_(send); data = LUT2[buffer_value & 0x0F]; send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); - pos--; - for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { - buffer_value = this->buffer_[pos]; + (((data & B11100000) >> 5) << 25) | clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; + for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { + buffer_value = *(buffer_ptr--); data = LUT2[(buffer_value >> 4) & 0x0F]; send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + (((data & B11100000) >> 5) << 25) | clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; data = LUT2[buffer_value & 0x0F]; send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); - pos--; + (((data & B11100000) >> 5) << 25) | clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; } - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = send; + GPIO.out_w1tc = get_data_pin_mask_() | clock; vscan_end_(); } delayMicroseconds(230); @@ -328,21 +320,21 @@ void Inkplate6::display1b_() { vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { - buffer_value = this->buffer_[pos]; data = 0b00000000; send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | (((data & B11100000) >> 5) << 25); hscan_start_(send); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + send |= clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; } - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = clock; + GPIO.out_w1tc = get_data_pin_mask_() | clock; vscan_end_(); } delayMicroseconds(230); @@ -368,8 +360,9 @@ void Inkplate6::display3b_() { clean_fast_(2, 1); clean_fast_(0, 11); + uint32_t clock = (1 << this->cl_pin_->get_pin()); for (int k = 0; k < 8; k++) { - uint32_t pos = this->get_buffer_length_() - 1; + const uint8_t *buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; uint32_t send; uint8_t pix1; uint8_t pix2; @@ -380,10 +373,10 @@ void Inkplate6::display3b_() { vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { - pix1 = this->buffer_[pos--]; - pix2 = this->buffer_[pos--]; - pix3 = this->buffer_[pos--]; - pix4 = this->buffer_[pos--]; + pix1 = (*buffer_ptr--); + pix2 = (*buffer_ptr--); + pix3 = (*buffer_ptr--); + pix4 = (*buffer_ptr--); pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) | (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0); pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | @@ -393,32 +386,32 @@ void Inkplate6::display3b_() { (((pixel & B11100000) >> 5) << 25); hscan_start_(send); send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) | - (((pixel2 & B11100000) >> 5) << 25); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); - - for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { - pix1 = this->buffer_[pos--]; - pix2 = this->buffer_[pos--]; - pix3 = this->buffer_[pos--]; - pix4 = this->buffer_[pos--]; + (((pixel2 & B11100000) >> 5) << 25) | clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; + + for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { + pix1 = (*buffer_ptr--); + pix2 = (*buffer_ptr--); + pix3 = (*buffer_ptr--); + pix4 = (*buffer_ptr--); pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) | (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0); pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) | - (((pixel & B11100000) >> 5) << 25); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + (((pixel & B11100000) >> 5) << 25) | clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) | - (((pixel2 & B11100000) >> 5) << 25); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + (((pixel2 & B11100000) >> 5) << 25) | clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; } - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = send; + GPIO.out_w1tc = get_data_pin_mask_() | clock; vscan_end_(); } delayMicroseconds(230); @@ -445,8 +438,8 @@ bool Inkplate6::partial_update_() { uint8_t diffw, diffb; uint32_t n = (this->get_buffer_length_() * 2) - 1; - for (int i = 0; i < this->get_height_internal(); i++) { - for (int j = 0; j < (this->get_width_internal() / 8); j++) { + for (int i = 0, im = this->get_height_internal(); i < im; i++) { + for (int j = 0, jm = (this->get_width_internal() / 8); j < jm; j++) { diffw = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & ~(this->partial_buffer_[pos]); diffb = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & this->partial_buffer_[pos]; pos--; @@ -457,23 +450,24 @@ bool Inkplate6::partial_update_() { ESP_LOGV(TAG, "Partial update buffer built after (%lums)", millis() - start_time); eink_on_(); + uint32_t clock = (1 << this->cl_pin_->get_pin()); for (int k = 0; k < 5; k++) { vscan_start_(); - n = (this->get_buffer_length_() * 2) - 1; + const uint8_t *data_ptr = &this->partial_buffer_2_[(this->get_buffer_length_() * 2) - 1]; for (int i = 0; i < this->get_height_internal(); i++) { - data = this->partial_buffer_2_[n--]; + data = *(data_ptr--); send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | (((data & B11100000) >> 5) << 25); hscan_start_(send); - for (int j = 0; j < (this->get_width_internal() / 4) - 1; j++) { - data = this->partial_buffer_2_[n--]; + for (int j = 0, jm = (this->get_width_internal() / 4) - 1; j < jm; j++) { + data = *(data_ptr--); send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + (((data & B11100000) >> 5) << 25) | clock; + GPIO.out_w1ts = send; + GPIO.out_w1tc = send; } - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = send; + GPIO.out_w1tc = get_data_pin_mask_() | clock; vscan_end_(); } delayMicroseconds(230); @@ -484,9 +478,7 @@ bool Inkplate6::partial_update_() { vscan_start_(); eink_off_(); - for (int i = 0; i < this->get_buffer_length_(); i++) { - this->buffer_[i] = this->partial_buffer_[i]; - } + memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_()); ESP_LOGV(TAG, "Partial update finished (%lums)", millis() - start_time); return true; } @@ -567,21 +559,22 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { uint32_t send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | (((data & B11100000) >> 5) << 25); + uint32_t clock = (1 << this->cl_pin_->get_pin()); for (int k = 0; k < rep; k++) { vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { hscan_start_(send); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); - for (int j = 0; j < this->get_width_internal() / 8; j++) { - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = clock; + for (int j = 0, jm = this->get_width_internal() / 8; j < jm; j++) { + GPIO.out_w1ts = clock; + GPIO.out_w1tc = clock; + GPIO.out_w1ts = clock; + GPIO.out_w1tc = clock; } - GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = clock; + GPIO.out_w1tc = get_data_pin_mask_() | clock; vscan_end_(); } delayMicroseconds(230); diff --git a/esphome/components/integration/__init__.py b/esphome/components/integration/__init__.py index 6f14e1003304..71a87b6ae508 100644 --- a/esphome/components/integration/__init__.py +++ b/esphome/components/integration/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@OttoWinter'] +CODEOWNERS = ["@OttoWinter"] diff --git a/esphome/components/integration/sensor.py b/esphome/components/integration/sensor.py index a354ab7433a7..81b588610e58 100644 --- a/esphome/components/integration/sensor.py +++ b/esphome/components/integration/sensor.py @@ -4,36 +4,41 @@ from esphome.components import sensor from esphome.const import CONF_ID, CONF_SENSOR, CONF_RESTORE -integration_ns = cg.esphome_ns.namespace('integration') -IntegrationSensor = integration_ns.class_('IntegrationSensor', sensor.Sensor, cg.Component) -ResetAction = integration_ns.class_('ResetAction', automation.Action) +integration_ns = cg.esphome_ns.namespace("integration") +IntegrationSensor = integration_ns.class_( + "IntegrationSensor", sensor.Sensor, cg.Component +) +ResetAction = integration_ns.class_("ResetAction", automation.Action) -IntegrationSensorTime = integration_ns.enum('IntegrationSensorTime') +IntegrationSensorTime = integration_ns.enum("IntegrationSensorTime") INTEGRATION_TIMES = { - 'ms': IntegrationSensorTime.INTEGRATION_SENSOR_TIME_MILLISECOND, - 's': IntegrationSensorTime.INTEGRATION_SENSOR_TIME_SECOND, - 'min': IntegrationSensorTime.INTEGRATION_SENSOR_TIME_MINUTE, - 'h': IntegrationSensorTime.INTEGRATION_SENSOR_TIME_HOUR, - 'd': IntegrationSensorTime.INTEGRATION_SENSOR_TIME_DAY, + "ms": IntegrationSensorTime.INTEGRATION_SENSOR_TIME_MILLISECOND, + "s": IntegrationSensorTime.INTEGRATION_SENSOR_TIME_SECOND, + "min": IntegrationSensorTime.INTEGRATION_SENSOR_TIME_MINUTE, + "h": IntegrationSensorTime.INTEGRATION_SENSOR_TIME_HOUR, + "d": IntegrationSensorTime.INTEGRATION_SENSOR_TIME_DAY, } -IntegrationMethod = integration_ns.enum('IntegrationMethod') +IntegrationMethod = integration_ns.enum("IntegrationMethod") INTEGRATION_METHODS = { - 'trapezoid': IntegrationMethod.INTEGRATION_METHOD_TRAPEZOID, - 'left': IntegrationMethod.INTEGRATION_METHOD_LEFT, - 'right': IntegrationMethod.INTEGRATION_METHOD_RIGHT, + "trapezoid": IntegrationMethod.INTEGRATION_METHOD_TRAPEZOID, + "left": IntegrationMethod.INTEGRATION_METHOD_LEFT, + "right": IntegrationMethod.INTEGRATION_METHOD_RIGHT, } -CONF_TIME_UNIT = 'time_unit' -CONF_INTEGRATION_METHOD = 'integration_method' +CONF_TIME_UNIT = "time_unit" +CONF_INTEGRATION_METHOD = "integration_method" -CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(IntegrationSensor), - cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), - 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=False): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(IntegrationSensor), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + 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=False): cv.boolean, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -49,9 +54,15 @@ def to_code(config): cg.add(var.set_restore(config[CONF_RESTORE])) -@automation.register_action('sensor.integration.reset', ResetAction, automation.maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(IntegrationSensor), -})) +@automation.register_action( + "sensor.integration.reset", + ResetAction, + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(IntegrationSensor), + } + ), +) def sensor_integration_reset_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/interval/__init__.py b/esphome/components/interval/__init__.py index be37526ad1ed..7d04a9e9abc3 100644 --- a/esphome/components/interval/__init__.py +++ b/esphome/components/interval/__init__.py @@ -3,15 +3,20 @@ from esphome import automation from esphome.const import CONF_ID, CONF_INTERVAL -CODEOWNERS = ['@esphome/core'] -interval_ns = cg.esphome_ns.namespace('interval') -IntervalTrigger = interval_ns.class_('IntervalTrigger', automation.Trigger.template(), - cg.PollingComponent) +CODEOWNERS = ["@esphome/core"] +interval_ns = cg.esphome_ns.namespace("interval") +IntervalTrigger = interval_ns.class_( + "IntervalTrigger", automation.Trigger.template(), cg.PollingComponent +) -CONFIG_SCHEMA = automation.validate_automation(cv.Schema({ - cv.GenerateID(): cv.declare_id(IntervalTrigger), - cv.Required(CONF_INTERVAL): cv.positive_time_period_milliseconds, -}).extend(cv.COMPONENT_SCHEMA)) +CONFIG_SCHEMA = automation.validate_automation( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(IntervalTrigger), + cv.Required(CONF_INTERVAL): cv.positive_time_period_milliseconds, + } + ).extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 63bc16dfa23b..f73fcb53fb21 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -1,12 +1,12 @@ import esphome.codegen as cg from esphome.core import coroutine_with_priority -CODEOWNERS = ['@OttoWinter'] -json_ns = cg.esphome_ns.namespace('json') +CODEOWNERS = ["@OttoWinter"] +json_ns = cg.esphome_ns.namespace("json") @coroutine_with_priority(1.0) def to_code(config): - cg.add_library('ArduinoJson-esphomelib', '5.13.3') - cg.add_define('USE_JSON') + cg.add_library("ArduinoJson-esphomelib", "5.13.3") + cg.add_define("USE_JSON") cg.add_global(json_ns.using) diff --git a/esphome/components/lcd_base/__init__.py b/esphome/components/lcd_base/__init__.py index bff194578cd1..c577d7f8bef1 100644 --- a/esphome/components/lcd_base/__init__.py +++ b/esphome/components/lcd_base/__init__.py @@ -4,8 +4,8 @@ from esphome.const import CONF_DIMENSIONS from esphome.core import coroutine -lcd_base_ns = cg.esphome_ns.namespace('lcd_base') -LCDDisplay = lcd_base_ns.class_('LCDDisplay', cg.PollingComponent) +lcd_base_ns = cg.esphome_ns.namespace("lcd_base") +LCDDisplay = lcd_base_ns.class_("LCDDisplay", cg.PollingComponent) def validate_lcd_dimensions(value): @@ -17,9 +17,11 @@ def validate_lcd_dimensions(value): return value -LCD_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({ - cv.Required(CONF_DIMENSIONS): validate_lcd_dimensions, -}).extend(cv.polling_component_schema('1s')) +LCD_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.Required(CONF_DIMENSIONS): validate_lcd_dimensions, + } +).extend(cv.polling_component_schema("1s")) @coroutine diff --git a/esphome/components/lcd_base/lcd_display.cpp b/esphome/components/lcd_base/lcd_display.cpp index 25ac143817b1..50bd818cdbe7 100644 --- a/esphome/components/lcd_base/lcd_display.cpp +++ b/esphome/components/lcd_base/lcd_display.cpp @@ -104,9 +104,7 @@ void HOT LCDDisplay::display() { } } void LCDDisplay::update() { - for (uint8_t i = 0; i < this->rows_ * this->columns_; i++) - this->buffer_[i] = ' '; - + this->clear(); this->call_writer(); this->display(); } @@ -149,9 +147,8 @@ void LCDDisplay::printf(const char *format, ...) { this->print(0, 0, buffer); } void LCDDisplay::clear() { - // clear display, also sets DDRAM address to 0 (home) - this->command_(LCD_DISPLAY_COMMAND_CLEAR_DISPLAY); - delay(2); + for (uint8_t i = 0; i < this->rows_ * this->columns_; i++) + this->buffer_[i] = ' '; } #ifdef USE_TIME void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, time::ESPTime time) { diff --git a/esphome/components/lcd_gpio/display.py b/esphome/components/lcd_gpio/display.py index 91498d59c986..28c6df2efd32 100644 --- a/esphome/components/lcd_gpio/display.py +++ b/esphome/components/lcd_gpio/display.py @@ -2,29 +2,41 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import lcd_base -from esphome.const import CONF_DATA_PINS, CONF_ENABLE_PIN, CONF_RS_PIN, CONF_RW_PIN, CONF_ID, \ - CONF_LAMBDA +from esphome.const import ( + CONF_DATA_PINS, + CONF_ENABLE_PIN, + CONF_RS_PIN, + CONF_RW_PIN, + CONF_ID, + CONF_LAMBDA, +) -AUTO_LOAD = ['lcd_base'] +AUTO_LOAD = ["lcd_base"] -lcd_gpio_ns = cg.esphome_ns.namespace('lcd_gpio') -GPIOLCDDisplay = lcd_gpio_ns.class_('GPIOLCDDisplay', lcd_base.LCDDisplay) +lcd_gpio_ns = cg.esphome_ns.namespace("lcd_gpio") +GPIOLCDDisplay = lcd_gpio_ns.class_("GPIOLCDDisplay", lcd_base.LCDDisplay) def validate_pin_length(value): if len(value) != 4 and len(value) != 8: - raise cv.Invalid("LCD Displays can either operate in 4-pin or 8-pin mode," - "not {}-pin mode".format(len(value))) + raise cv.Invalid( + "LCD Displays can either operate in 4-pin or 8-pin mode," + "not {}-pin mode".format(len(value)) + ) return value -CONFIG_SCHEMA = lcd_base.LCD_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(GPIOLCDDisplay), - cv.Required(CONF_DATA_PINS): cv.All([pins.gpio_output_pin_schema], validate_pin_length), - cv.Required(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_RS_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_RW_PIN): pins.gpio_output_pin_schema, -}) +CONFIG_SCHEMA = lcd_base.LCD_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(GPIOLCDDisplay), + cv.Required(CONF_DATA_PINS): cv.All( + [pins.gpio_output_pin_schema], validate_pin_length + ), + cv.Required(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_RS_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_RW_PIN): pins.gpio_output_pin_schema, + } +) def to_code(config): @@ -45,7 +57,9 @@ def to_code(config): cg.add(var.set_rw_pin(rw)) if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], - [(GPIOLCDDisplay.operator('ref'), 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], + [(GPIOLCDDisplay.operator("ref"), "it")], + return_type=cg.void, + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/lcd_pcf8574/display.py b/esphome/components/lcd_pcf8574/display.py index 2bbb3a2f7b4f..6499a8369e70 100644 --- a/esphome/components/lcd_pcf8574/display.py +++ b/esphome/components/lcd_pcf8574/display.py @@ -3,15 +3,19 @@ from esphome.components import lcd_base, i2c from esphome.const import CONF_ID, CONF_LAMBDA -DEPENDENCIES = ['i2c'] -AUTO_LOAD = ['lcd_base'] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["lcd_base"] -lcd_pcf8574_ns = cg.esphome_ns.namespace('lcd_pcf8574') -PCF8574LCDDisplay = lcd_pcf8574_ns.class_('PCF8574LCDDisplay', lcd_base.LCDDisplay, i2c.I2CDevice) +lcd_pcf8574_ns = cg.esphome_ns.namespace("lcd_pcf8574") +PCF8574LCDDisplay = lcd_pcf8574_ns.class_( + "PCF8574LCDDisplay", lcd_base.LCDDisplay, i2c.I2CDevice +) -CONFIG_SCHEMA = lcd_base.LCD_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(PCF8574LCDDisplay), -}).extend(i2c.i2c_device_schema(0x3F)) +CONFIG_SCHEMA = lcd_base.LCD_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PCF8574LCDDisplay), + } +).extend(i2c.i2c_device_schema(0x3F)) def to_code(config): @@ -20,7 +24,9 @@ def to_code(config): yield i2c.register_i2c_device(var, config) if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], - [(PCF8574LCDDisplay.operator('ref'), 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], + [(PCF8574LCDDisplay.operator("ref"), "it")], + return_type=cg.void, + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ledc/__init__.py b/esphome/components/ledc/__init__.py index 6f14e1003304..71a87b6ae508 100644 --- a/esphome/components/ledc/__init__.py +++ b/esphome/components/ledc/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@OttoWinter'] +CODEOWNERS = ["@OttoWinter"] diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index b608e9bbf7ff..df746fff4fbc 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -2,19 +2,25 @@ from esphome.components import output import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_BIT_DEPTH, CONF_CHANNEL, CONF_FREQUENCY, \ - CONF_ID, CONF_PIN, ESP_PLATFORM_ESP32 +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) + return 80e6 / (2 ** bit_depth) def calc_min_frequency(bit_depth): - max_div_num = ((2**20) - 1) / 256.0 - return 80e6 / (max_div_num * (2**bit_depth)) + max_div_num = ((2 ** 20) - 1) / 256.0 + return 80e6 / (max_div_num * (2 ** bit_depth)) def validate_frequency(value): @@ -22,27 +28,34 @@ def validate_frequency(value): min_freq = calc_min_frequency(20) max_freq = calc_max_frequency(1) if value < min_freq: - raise cv.Invalid("This frequency setting is not possible, please choose a higher " - "frequency (at least {}Hz)".format(int(min_freq))) + raise cv.Invalid( + "This frequency setting is not possible, please choose a higher " + "frequency (at least {}Hz)".format(int(min_freq)) + ) if value > 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( + "This frequency setting is not possible, please choose a lower " + "frequency (at most {}Hz)".format(int(max_freq)) + ) return value -ledc_ns = cg.esphome_ns.namespace('ledc') -LEDCOutput = ledc_ns.class_('LEDCOutput', output.FloatOutput, cg.Component) -SetFrequencyAction = ledc_ns.class_('SetFrequencyAction', automation.Action) +ledc_ns = cg.esphome_ns.namespace("ledc") +LEDCOutput = ledc_ns.class_("LEDCOutput", output.FloatOutput, cg.Component) +SetFrequencyAction = ledc_ns.class_("SetFrequencyAction", automation.Action) -CONFIG_SCHEMA = 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_CHANNEL): cv.int_range(min=0, max=15), - - cv.Optional(CONF_BIT_DEPTH): cv.invalid("The bit_depth option has been removed in v1.14, the " - "best bit depth is now automatically calculated."), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = 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_CHANNEL): cv.int_range(min=0, max=15), + cv.Optional(CONF_BIT_DEPTH): cv.invalid( + "The bit_depth option has been removed in v1.14, the " + "best bit depth is now automatically calculated." + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -55,10 +68,16 @@ def to_code(config): cg.add(var.set_frequency(config[CONF_FREQUENCY])) -@automation.register_action('output.ledc.set_frequency', SetFrequencyAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(LEDCOutput), - cv.Required(CONF_FREQUENCY): cv.templatable(validate_frequency), -})) +@automation.register_action( + "output.ledc.set_frequency", + SetFrequencyAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(LEDCOutput), + cv.Required(CONF_FREQUENCY): cv.templatable(validate_frequency), + } + ), +) def ledc_set_frequency_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 9d58fa47bf50..034dbdaf349c 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -2,62 +2,104 @@ import esphome.config_validation as cv import esphome.automation as auto from esphome.components import mqtt, power_supply -from esphome.const import CONF_COLOR_CORRECT, \ - CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_ID, \ - CONF_INTERNAL, CONF_NAME, CONF_MQTT_ID, CONF_POWER_SUPPLY, CONF_RESTORE_MODE, \ - CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID +from esphome.const import ( + CONF_COLOR_CORRECT, + CONF_DEFAULT_TRANSITION_LENGTH, + CONF_EFFECTS, + CONF_GAMMA_CORRECT, + CONF_ID, + CONF_INTERNAL, + CONF_NAME, + CONF_MQTT_ID, + CONF_POWER_SUPPLY, + CONF_RESTORE_MODE, + CONF_ON_TURN_OFF, + CONF_ON_TURN_ON, + CONF_TRIGGER_ID, +) from esphome.core import coroutine, coroutine_with_priority from .automation import light_control_to_code # noqa -from .effects import validate_effects, BINARY_EFFECTS, \ - MONOCHROMATIC_EFFECTS, RGB_EFFECTS, ADDRESSABLE_EFFECTS, EFFECTS_REGISTRY +from .effects import ( + validate_effects, + BINARY_EFFECTS, + MONOCHROMATIC_EFFECTS, + RGB_EFFECTS, + ADDRESSABLE_EFFECTS, + EFFECTS_REGISTRY, +) from .types import ( # noqa - LightState, AddressableLightState, light_ns, LightOutput, AddressableLight, \ - LightTurnOnTrigger, LightTurnOffTrigger) - -CODEOWNERS = ['@esphome/core'] + LightState, + AddressableLightState, + light_ns, + LightOutput, + AddressableLight, + LightTurnOnTrigger, + LightTurnOffTrigger, +) + +CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True -LightRestoreMode = light_ns.enum('LightRestoreMode') +LightRestoreMode = light_ns.enum("LightRestoreMode") RESTORE_MODES = { - 'RESTORE_DEFAULT_OFF': LightRestoreMode.LIGHT_RESTORE_DEFAULT_OFF, - 'RESTORE_DEFAULT_ON': LightRestoreMode.LIGHT_RESTORE_DEFAULT_ON, - 'ALWAYS_OFF': LightRestoreMode.LIGHT_ALWAYS_OFF, - 'ALWAYS_ON': LightRestoreMode.LIGHT_ALWAYS_ON, + "RESTORE_DEFAULT_OFF": LightRestoreMode.LIGHT_RESTORE_DEFAULT_OFF, + "RESTORE_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_DEFAULT_ON, + "ALWAYS_OFF": LightRestoreMode.LIGHT_ALWAYS_OFF, + "ALWAYS_ON": LightRestoreMode.LIGHT_ALWAYS_ON, } -LIGHT_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(LightState), - cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTJSONLightComponent), - cv.Optional(CONF_RESTORE_MODE, default='restore_default_off'): - cv.enum(RESTORE_MODES, upper=True, space='_'), - cv.Optional(CONF_ON_TURN_ON): auto.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOnTrigger), - }), - cv.Optional(CONF_ON_TURN_OFF): auto.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger), - }), -}) - -BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend({ - cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS), -}) - -BRIGHTNESS_ONLY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend({ - cv.Optional(CONF_GAMMA_CORRECT, default=2.8): cv.positive_float, - cv.Optional(CONF_DEFAULT_TRANSITION_LENGTH, default='1s'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_EFFECTS): validate_effects(MONOCHROMATIC_EFFECTS), -}) - -RGB_LIGHT_SCHEMA = BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend({ - cv.Optional(CONF_EFFECTS): validate_effects(RGB_EFFECTS), -}) - -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), -}) +LIGHT_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(LightState), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent), + cv.Optional(CONF_RESTORE_MODE, default="restore_default_off"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), + cv.Optional(CONF_ON_TURN_ON): auto.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOnTrigger), + } + ), + cv.Optional(CONF_ON_TURN_OFF): auto.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger), + } + ), + } +) + +BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend( + { + cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS), + } +) + +BRIGHTNESS_ONLY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend( + { + cv.Optional(CONF_GAMMA_CORRECT, default=2.8): cv.positive_float, + cv.Optional( + CONF_DEFAULT_TRANSITION_LENGTH, default="1s" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_EFFECTS): validate_effects(MONOCHROMATIC_EFFECTS), + } +) + +RGB_LIGHT_SCHEMA = BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( + { + cv.Optional(CONF_EFFECTS): validate_effects(RGB_EFFECTS), + } +) + +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), + } +) @coroutine @@ -66,10 +108,16 @@ def setup_light_core_(light_var, output_var, config): if CONF_INTERNAL in config: cg.add(light_var.set_internal(config[CONF_INTERNAL])) if CONF_DEFAULT_TRANSITION_LENGTH in config: - cg.add(light_var.set_default_transition_length(config[CONF_DEFAULT_TRANSITION_LENGTH])) + cg.add( + light_var.set_default_transition_length( + config[CONF_DEFAULT_TRANSITION_LENGTH] + ) + ) if CONF_GAMMA_CORRECT in config: cg.add(light_var.set_gamma_correct(config[CONF_GAMMA_CORRECT])) - effects = yield cg.build_registry_list(EFFECTS_REGISTRY, config.get(CONF_EFFECTS, [])) + effects = yield cg.build_registry_list( + EFFECTS_REGISTRY, config.get(CONF_EFFECTS, []) + ) cg.add(light_var.add_effects(effects)) for conf in config.get(CONF_ON_TURN_ON, []): @@ -101,5 +149,5 @@ def register_light(output_var, config): @coroutine_with_priority(100.0) def to_code(config): - cg.add_define('USE_LIGHT') + cg.add_define("USE_LIGHT") cg.add_global(light_ns.using) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index b5dc70a083d2..236e5cede6fc 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -6,10 +6,7 @@ namespace light { static const char *TAG = "light.addressable"; -const ESPColor ESPColor::BLACK = ESPColor(0, 0, 0, 0); -const ESPColor ESPColor::WHITE = ESPColor(255, 255, 255, 255); - -ESPColor ESPHSVColor::to_rgb() const { +Color ESPHSVColor::to_rgb() const { // based on FastLED's hsv rainbow to rgb const uint8_t hue = this->hue; const uint8_t sat = this->saturation; @@ -19,7 +16,7 @@ ESPColor ESPHSVColor::to_rgb() const { // third of the offset, 255/3 = 85 (actually only up to 82; 164) const uint8_t third = esp_scale8(offset8, 85); const uint8_t two_thirds = esp_scale8(offset8, 170); - ESPColor rgb(255, 255, 255, 0); + Color rgb(255, 255, 255, 0); switch (hue >> 5) { case 0b000: rgb.r = 255 - third; @@ -76,7 +73,7 @@ ESPColor ESPHSVColor::to_rgb() const { return rgb; } -void ESPRangeView::set(const ESPColor &color) { +void ESPRangeView::set(const Color &color) { for (int32_t i = this->begin_; i < this->end_; i++) { (*this->parent_)[i] = color; } @@ -179,12 +176,12 @@ void AddressableLight::call_setup() { #endif } -ESPColor esp_color_from_light_color_values(LightColorValues val) { +Color esp_color_from_light_color_values(LightColorValues val) { auto r = static_cast(roundf(val.get_red() * 255.0f)); auto g = static_cast(roundf(val.get_green() * 255.0f)); auto b = static_cast(roundf(val.get_blue() * 255.0f)); auto w = static_cast(roundf(val.get_white() * val.get_state() * 255.0f)); - return ESPColor(r, g, b, w); + return Color(r, g, b, w); } void AddressableLight::write_state(LightState *state) { @@ -219,7 +216,7 @@ void AddressableLight::write_state(LightState *state) { this->last_transition_progress_ = new_progress; auto end_values = state->transformer_->get_end_values(); - ESPColor target_color = esp_color_from_light_color_values(end_values); + Color target_color = esp_color_from_light_color_values(end_values); // our transition will handle brightness, disable brightness in correction. this->correction_.set_local_brightness(255); @@ -247,7 +244,7 @@ void AddressableLight::write_state(LightState *state) { if (alpha8 != 0) { uint8_t inv_alpha8 = 255 - alpha8; - ESPColor add = target_color * alpha8; + Color add = target_color * alpha8; for (auto led : *this) led = add + led.get() * inv_alpha8; diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 4e7ec4b93146..39bd905c65d6 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" +#include "esphome/core/color.h" #include "light_output.h" #include "light_state.h" @@ -12,151 +13,7 @@ namespace esphome { namespace light { -inline static uint8_t esp_scale8(uint8_t i, uint8_t scale) { return (uint16_t(i) * (1 + uint16_t(scale))) / 256; } - -struct ESPColor { - union { - struct { - union { - uint8_t r; - uint8_t red; - }; - union { - uint8_t g; - uint8_t green; - }; - union { - uint8_t b; - uint8_t blue; - }; - union { - uint8_t w; - uint8_t white; - }; - }; - 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), - g(green), - b(blue), - w(white) {} - inline ESPColor(uint8_t red, uint8_t green, uint8_t blue) ALWAYS_INLINE : r(red), g(green), b(blue), w(0) {} - inline ESPColor(uint32_t colorcode) ALWAYS_INLINE : r((colorcode >> 16) & 0xFF), - g((colorcode >> 8) & 0xFF), - b((colorcode >> 0) & 0xFF), - w((colorcode >> 24) & 0xFF) {} - inline ESPColor(const ESPColor &rhs) ALWAYS_INLINE { - this->r = rhs.r; - this->g = rhs.g; - this->b = rhs.b; - this->w = rhs.w; - } - 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; - this->b = rhs.b; - this->w = rhs.w; - return *this; - } - inline ESPColor &operator=(uint32_t colorcode) ALWAYS_INLINE { - this->w = (colorcode >> 24) & 0xFF; - this->r = (colorcode >> 16) & 0xFF; - this->g = (colorcode >> 8) & 0xFF; - this->b = (colorcode >> 0) & 0xFF; - return *this; - } - inline uint8_t &operator[](uint8_t x) ALWAYS_INLINE { return this->raw[x]; } - inline ESPColor operator*(uint8_t scale) const ALWAYS_INLINE { - return ESPColor(esp_scale8(this->red, scale), esp_scale8(this->green, scale), esp_scale8(this->blue, scale), - esp_scale8(this->white, scale)); - } - inline ESPColor &operator*=(uint8_t scale) ALWAYS_INLINE { - this->red = esp_scale8(this->red, scale); - this->green = esp_scale8(this->green, scale); - this->blue = esp_scale8(this->blue, scale); - this->white = esp_scale8(this->white, scale); - return *this; - } - inline ESPColor operator*(const ESPColor &scale) const ALWAYS_INLINE { - return ESPColor(esp_scale8(this->red, scale.red), esp_scale8(this->green, scale.green), - esp_scale8(this->blue, scale.blue), esp_scale8(this->white, scale.white)); - } - inline ESPColor &operator*=(const ESPColor &scale) ALWAYS_INLINE { - this->red = esp_scale8(this->red, scale.red); - this->green = esp_scale8(this->green, scale.green); - this->blue = esp_scale8(this->blue, scale.blue); - this->white = esp_scale8(this->white, scale.white); - return *this; - } - inline ESPColor operator+(const ESPColor &add) const ALWAYS_INLINE { - ESPColor ret; - if (uint8_t(add.r + this->r) < this->r) - ret.r = 255; - else - ret.r = this->r + add.r; - if (uint8_t(add.g + this->g) < this->g) - ret.g = 255; - else - ret.g = this->g + add.g; - if (uint8_t(add.b + this->b) < this->b) - ret.b = 255; - else - ret.b = this->b + add.b; - if (uint8_t(add.w + this->w) < this->w) - ret.w = 255; - else - ret.w = this->w + add.w; - return ret; - } - inline ESPColor &operator+=(const ESPColor &add) ALWAYS_INLINE { return *this = (*this) + add; } - inline ESPColor operator+(uint8_t add) const ALWAYS_INLINE { return (*this) + ESPColor(add, add, add, add); } - inline ESPColor &operator+=(uint8_t add) ALWAYS_INLINE { return *this = (*this) + add; } - inline ESPColor operator-(const ESPColor &subtract) const ALWAYS_INLINE { - ESPColor ret; - if (subtract.r > this->r) - ret.r = 0; - else - ret.r = this->r - subtract.r; - if (subtract.g > this->g) - ret.g = 0; - else - ret.g = this->g - subtract.g; - if (subtract.b > this->b) - ret.b = 0; - else - ret.b = this->b - subtract.b; - if (subtract.w > this->w) - ret.w = 0; - else - ret.w = this->w - subtract.w; - return ret; - } - inline ESPColor &operator-=(const ESPColor &subtract) ALWAYS_INLINE { return *this = (*this) - subtract; } - inline ESPColor operator-(uint8_t subtract) const ALWAYS_INLINE { - return (*this) - ESPColor(subtract, subtract, subtract, subtract); - } - inline ESPColor &operator-=(uint8_t subtract) ALWAYS_INLINE { return *this = (*this) - subtract; } - static ESPColor random_color() { - uint32_t rand = random_uint32(); - uint8_t w = rand >> 24; - uint8_t r = rand >> 16; - uint8_t g = rand >> 8; - uint8_t b = rand >> 0; - const uint16_t max_rgb = std::max(r, std::max(g, b)); - return ESPColor(uint8_t((uint16_t(r) * 255U / max_rgb)), uint8_t((uint16_t(g) * 255U / max_rgb)), - uint8_t((uint16_t(b) * 255U / max_rgb)), w); - } - ESPColor fade_to_white(uint8_t amnt) const { return ESPColor(255, 255, 255, 255) - (*this * amnt); } - ESPColor fade_to_black(uint8_t amnt) const { return *this * amnt; } - ESPColor lighten(uint8_t delta) const { return *this + delta; } - ESPColor darken(uint8_t delta) const { return *this - delta; } - - static const ESPColor BLACK; - static const ESPColor WHITE; -}; +using ESPColor = Color; struct ESPHSVColor { union { @@ -181,19 +38,19 @@ struct ESPHSVColor { inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue), saturation(saturation), value(value) {} - ESPColor to_rgb() const; + Color to_rgb() const; }; class ESPColorCorrection { public: ESPColorCorrection() : max_brightness_(255, 255, 255, 255) {} - void set_max_brightness(const ESPColor &max_brightness) { this->max_brightness_ = max_brightness; } + void set_max_brightness(const Color &max_brightness) { this->max_brightness_ = max_brightness; } void set_local_brightness(uint8_t local_brightness) { this->local_brightness_ = local_brightness; } void calculate_gamma_table(float gamma); - inline ESPColor color_correct(ESPColor color) const ALWAYS_INLINE { + inline Color color_correct(Color color) const ALWAYS_INLINE { // corrected = (uncorrected * max_brightness * local_brightness) ^ gamma - return ESPColor(this->color_correct_red(color.red), this->color_correct_green(color.green), - this->color_correct_blue(color.blue), this->color_correct_white(color.white)); + return Color(this->color_correct_red(color.red), this->color_correct_green(color.green), + this->color_correct_blue(color.blue), this->color_correct_white(color.white)); } inline uint8_t color_correct_red(uint8_t red) const ALWAYS_INLINE { uint8_t res = esp_scale8(esp_scale8(red, this->max_brightness_.red), this->local_brightness_); @@ -212,10 +69,10 @@ class ESPColorCorrection { uint8_t res = esp_scale8(white, this->max_brightness_.white); return this->gamma_table_[res]; } - inline ESPColor color_uncorrect(ESPColor color) const ALWAYS_INLINE { + inline Color color_uncorrect(Color color) const ALWAYS_INLINE { // uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness) - return ESPColor(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green), - this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white)); + return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green), + this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white)); } inline uint8_t color_uncorrect_red(uint8_t red) const ALWAYS_INLINE { if (this->max_brightness_.red == 0 || this->local_brightness_ == 0) @@ -249,13 +106,13 @@ class ESPColorCorrection { protected: uint8_t gamma_table_[256]; uint8_t gamma_reverse_table_[256]; - ESPColor max_brightness_; + Color max_brightness_; uint8_t local_brightness_{255}; }; class ESPColorSettable { public: - virtual void set(const ESPColor &color) = 0; + virtual void set(const Color &color) = 0; virtual void set_red(uint8_t red) = 0; virtual void set_green(uint8_t green) = 0; virtual void set_blue(uint8_t blue) = 0; @@ -267,7 +124,7 @@ class ESPColorSettable { virtual void darken(uint8_t delta) = 0; void set(const ESPHSVColor &color) { this->set_hsv(color); } void set_hsv(const ESPHSVColor &color) { - ESPColor rgb = color.to_rgb(); + Color rgb = color.to_rgb(); this->set_rgb(rgb.r, rgb.g, rgb.b); } void set_rgb(uint8_t red, uint8_t green, uint8_t blue) { @@ -291,7 +148,7 @@ class ESPColorView : public ESPColorSettable { white_(white), effect_data_(effect_data), color_correction_(color_correction) {} - ESPColorView &operator=(const ESPColor &rhs) { + ESPColorView &operator=(const Color &rhs) { this->set(rhs); return *this; } @@ -299,7 +156,7 @@ class ESPColorView : public ESPColorSettable { this->set_hsv(rhs); return *this; } - void set(const ESPColor &color) override { this->set_rgbw(color.r, color.g, color.b, color.w); } + void set(const Color &color) override { this->set_rgbw(color.r, color.g, color.b, color.w); } void set_red(uint8_t red) override { *this->red_ = this->color_correction_->color_correct_red(red); } void set_green(uint8_t green) override { *this->green_ = this->color_correction_->color_correct_green(green); } void set_blue(uint8_t blue) override { *this->blue_ = this->color_correction_->color_correct_blue(blue); } @@ -317,7 +174,7 @@ class ESPColorView : public ESPColorSettable { void fade_to_black(uint8_t amnt) override { this->set(this->get().fade_to_black(amnt)); } void lighten(uint8_t delta) override { this->set(this->get().lighten(delta)); } void darken(uint8_t delta) override { this->set(this->get().darken(delta)); } - ESPColor get() const { return ESPColor(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); } + Color get() const { return Color(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); } uint8_t get_red() const { return this->color_correction_->color_uncorrect_red(*this->red_); } uint8_t get_red_raw() const { return *this->red_; } uint8_t get_green() const { return this->color_correction_->color_uncorrect_green(*this->green_); } @@ -370,8 +227,8 @@ class ESPRangeView : public ESPColorSettable { ESPRangeIterator begin(); ESPRangeIterator end(); - void set(const ESPColor &color) override; - ESPRangeView &operator=(const ESPColor &rhs) { + void set(const Color &color) override; + ESPRangeView &operator=(const Color &rhs) { this->set(rhs); return *this; } @@ -454,8 +311,8 @@ class AddressableLight : public LightOutput, public Component { void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; } void write_state(LightState *state) override; void set_correction(float red, float green, float blue, float white = 1.0f) { - this->correction_.set_max_brightness(ESPColor(uint8_t(roundf(red * 255.0f)), uint8_t(roundf(green * 255.0f)), - uint8_t(roundf(blue * 255.0f)), uint8_t(roundf(white * 255.0f)))); + this->correction_.set_max_brightness(Color(uint8_t(roundf(red * 255.0f)), uint8_t(roundf(green * 255.0f)), + uint8_t(roundf(blue * 255.0f)), uint8_t(roundf(white * 255.0f)))); } void setup_state(LightState *state) override { this->correction_.calculate_gamma_table(state->get_gamma_correct()); diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index e6528fcd8ad8..8d4d37ec3443 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -34,13 +34,13 @@ class AddressableLightEffect : public LightEffect { this->start(); } void stop() override { this->get_addressable_()->set_effect_active(false); } - virtual void apply(AddressableLight &it, const ESPColor ¤t_color) = 0; + virtual void apply(AddressableLight &it, const Color ¤t_color) = 0; void apply() override { LightColorValues color = this->state_->remote_values; // not using any color correction etc. that will be handled by the addressable layer - ESPColor current_color = - ESPColor(static_cast(color.get_red() * 255), static_cast(color.get_green() * 255), - static_cast(color.get_blue() * 255), static_cast(color.get_white() * 255)); + Color current_color = + Color(static_cast(color.get_red() * 255), static_cast(color.get_green() * 255), + static_cast(color.get_blue() * 255), static_cast(color.get_white() * 255)); this->apply(*this->get_addressable_(), current_color); } @@ -51,11 +51,11 @@ class AddressableLightEffect : public LightEffect { class AddressableLambdaLightEffect : public AddressableLightEffect { public: AddressableLambdaLightEffect(const std::string &name, - const std::function &f, + const std::function &f, uint32_t update_interval) : AddressableLightEffect(name), f_(f), update_interval_(update_interval) {} void start() override { this->initial_run_ = true; } - void apply(AddressableLight &it, const ESPColor ¤t_color) override { + void apply(AddressableLight &it, const Color ¤t_color) override { const uint32_t now = millis(); if (now - this->last_run_ >= this->update_interval_) { this->last_run_ = now; @@ -65,7 +65,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect { } protected: - std::function f_; + std::function f_; uint32_t update_interval_; uint32_t last_run_{0}; bool initial_run_; @@ -74,7 +74,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect { class AddressableRainbowLightEffect : public AddressableLightEffect { public: explicit AddressableRainbowLightEffect(const std::string &name) : AddressableLightEffect(name) {} - void apply(AddressableLight &it, const ESPColor ¤t_color) override { + void apply(AddressableLight &it, const Color ¤t_color) override { ESPHSVColor hsv; hsv.value = 255; hsv.saturation = 240; @@ -106,7 +106,7 @@ class AddressableColorWipeEffect : public AddressableLightEffect { void set_colors(const std::vector &colors) { this->colors_ = colors; } void set_add_led_interval(uint32_t add_led_interval) { this->add_led_interval_ = add_led_interval; } void set_reverse(bool reverse) { this->reverse_ = reverse; } - void apply(AddressableLight &it, const ESPColor ¤t_color) override { + void apply(AddressableLight &it, const Color ¤t_color) override { const uint32_t now = millis(); if (now - this->last_add_ < this->add_led_interval_) return; @@ -116,7 +116,7 @@ class AddressableColorWipeEffect : public AddressableLightEffect { else it.shift_right(1); const AddressableColorWipeEffectColor color = this->colors_[this->at_color_]; - const ESPColor esp_color = ESPColor(color.r, color.g, color.b, color.w); + const Color esp_color = Color(color.r, color.g, color.b, color.w); if (this->reverse_) it[-1] = esp_color; else @@ -126,7 +126,7 @@ class AddressableColorWipeEffect : public AddressableLightEffect { this->at_color_ = (this->at_color_ + 1) % this->colors_.size(); AddressableColorWipeEffectColor &new_color = this->colors_[this->at_color_]; if (new_color.random) { - ESPColor c = ESPColor::random_color(); + Color c = Color::random_color(); new_color.r = c.r; new_color.g = c.g; new_color.b = c.b; @@ -148,8 +148,8 @@ class AddressableScanEffect : public AddressableLightEffect { explicit AddressableScanEffect(const std::string &name) : AddressableLightEffect(name) {} void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; } void set_scan_width(uint32_t scan_width) { this->scan_width_ = scan_width; } - void apply(AddressableLight &it, const ESPColor ¤t_color) override { - it.all() = ESPColor::BLACK; + void apply(AddressableLight &it, const Color ¤t_color) override { + it.all() = COLOR_BLACK; for (auto i = 0; i < this->scan_width_; i++) { it[this->at_led_ + i] = current_color; @@ -181,7 +181,7 @@ class AddressableScanEffect : public AddressableLightEffect { class AddressableTwinkleEffect : public AddressableLightEffect { public: explicit AddressableTwinkleEffect(const std::string &name) : AddressableLightEffect(name) {} - void apply(AddressableLight &addressable, const ESPColor ¤t_color) override { + void apply(AddressableLight &addressable, const Color ¤t_color) override { const uint32_t now = millis(); uint8_t pos_add = 0; if (now - this->last_progress_ > this->progress_interval_) { @@ -199,7 +199,7 @@ class AddressableTwinkleEffect : public AddressableLightEffect { else view.set_effect_data(new_pos); } else { - view = ESPColor::BLACK; + view = COLOR_BLACK; } } while (random_float() < this->twinkle_probability_) { @@ -221,7 +221,7 @@ class AddressableTwinkleEffect : public AddressableLightEffect { class AddressableRandomTwinkleEffect : public AddressableLightEffect { public: explicit AddressableRandomTwinkleEffect(const std::string &name) : AddressableLightEffect(name) {} - void apply(AddressableLight &it, const ESPColor ¤t_color) override { + void apply(AddressableLight &it, const Color ¤t_color) override { const uint32_t now = millis(); uint8_t pos_add = 0; if (now - this->last_progress_ > this->progress_interval_) { @@ -237,7 +237,7 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect { if (color == 0) { view = current_color * sine; } else { - view = ESPColor(((color >> 2) & 1) * sine, ((color >> 1) & 1) * sine, ((color >> 0) & 1) * sine); + view = Color(((color >> 2) & 1) * sine, ((color >> 1) & 1) * sine, ((color >> 0) & 1) * sine); } const uint8_t new_x = x + pos_add; if (new_x > 0b11111) @@ -245,7 +245,7 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect { else view.set_effect_data((new_x << 3) | color); } else { - view = ESPColor(0, 0, 0, 0); + view = Color(0, 0, 0, 0); } } while (random_float() < this->twinkle_probability_) { @@ -270,9 +270,9 @@ class AddressableFireworksEffect : public AddressableLightEffect { explicit AddressableFireworksEffect(const std::string &name) : AddressableLightEffect(name) {} void start() override { auto &it = *this->get_addressable_(); - it.all() = ESPColor::BLACK; + it.all() = COLOR_BLACK; } - void apply(AddressableLight &it, const ESPColor ¤t_color) override { + void apply(AddressableLight &it, const Color ¤t_color) override { const uint32_t now = millis(); if (now - this->last_update_ < this->update_interval_) return; @@ -280,7 +280,7 @@ class AddressableFireworksEffect : public AddressableLightEffect { // "invert" the fade out parameter so that higher values make fade out faster const uint8_t fade_out_mult = 255u - this->fade_out_rate_; for (auto view : it) { - ESPColor target = view.get() * fade_out_mult; + Color target = view.get() * fade_out_mult; if (target.r < 64) target *= 170; view = target; @@ -294,7 +294,7 @@ class AddressableFireworksEffect : public AddressableLightEffect { if (random_float() < this->spark_probability_) { const size_t pos = random_uint32() % it.size(); if (this->use_random_color_) { - it[pos] = ESPColor::random_color(); + it[pos] = Color::random_color(); } else { it[pos] = current_color; } @@ -316,7 +316,7 @@ class AddressableFireworksEffect : public AddressableLightEffect { class AddressableFlickerEffect : public AddressableLightEffect { public: explicit AddressableFlickerEffect(const std::string &name) : AddressableLightEffect(name) {} - void apply(AddressableLight &it, const ESPColor ¤t_color) override { + void apply(AddressableLight &it, const Color ¤t_color) override { const uint32_t now = millis(); const uint8_t intensity = this->intensity_; const uint8_t inv_intensity = 255 - intensity; diff --git a/esphome/components/light/automation.py b/esphome/components/light/automation.py index 9e14246c0f92..7f1c6d9cb993 100644 --- a/esphome/components/light/automation.py +++ b/esphome/components/light/automation.py @@ -1,54 +1,102 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ID, CONF_TRANSITION_LENGTH, CONF_STATE, CONF_FLASH_LENGTH, \ - CONF_EFFECT, CONF_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, \ - CONF_COLOR_TEMPERATURE, CONF_RANGE_FROM, CONF_RANGE_TO -from .types import DimRelativeAction, ToggleAction, LightState, LightControlAction, \ - AddressableLightState, AddressableSet, LightIsOnCondition, LightIsOffCondition - - -@automation.register_action('light.toggle', ToggleAction, automation.maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(LightState), - cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds), -})) +from esphome.const import ( + CONF_ID, + CONF_TRANSITION_LENGTH, + CONF_STATE, + CONF_FLASH_LENGTH, + CONF_EFFECT, + CONF_BRIGHTNESS, + CONF_RED, + CONF_GREEN, + CONF_BLUE, + CONF_WHITE, + CONF_COLOR_TEMPERATURE, + CONF_RANGE_FROM, + CONF_RANGE_TO, +) +from .types import ( + DimRelativeAction, + ToggleAction, + LightState, + LightControlAction, + AddressableLightState, + AddressableSet, + LightIsOnCondition, + LightIsOffCondition, +) + + +@automation.register_action( + "light.toggle", + ToggleAction, + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(LightState), + cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable( + cv.positive_time_period_milliseconds + ), + } + ), +) def light_toggle_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) if CONF_TRANSITION_LENGTH in config: - template_ = yield cg.templatable(config[CONF_TRANSITION_LENGTH], args, cg.uint32) + template_ = yield cg.templatable( + config[CONF_TRANSITION_LENGTH], args, cg.uint32 + ) cg.add(var.set_transition_length(template_)) yield var -LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.use_id(LightState), - cv.Optional(CONF_STATE): cv.templatable(cv.boolean), - cv.Exclusive(CONF_TRANSITION_LENGTH, 'transformer'): - cv.templatable(cv.positive_time_period_milliseconds), - cv.Exclusive(CONF_FLASH_LENGTH, 'transformer'): - cv.templatable(cv.positive_time_period_milliseconds), - cv.Exclusive(CONF_EFFECT, 'transformer'): cv.templatable(cv.string), - cv.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage), - cv.Optional(CONF_RED): cv.templatable(cv.percentage), - cv.Optional(CONF_GREEN): cv.templatable(cv.percentage), - cv.Optional(CONF_BLUE): cv.templatable(cv.percentage), - cv.Optional(CONF_WHITE): cv.templatable(cv.percentage), - cv.Optional(CONF_COLOR_TEMPERATURE): cv.templatable(cv.color_temperature), -}) -LIGHT_TURN_OFF_ACTION_SCHEMA = automation.maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(LightState), - cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds), - cv.Optional(CONF_STATE, default=False): False, -}) -LIGHT_TURN_ON_ACTION_SCHEMA = automation.maybe_simple_id(LIGHT_CONTROL_ACTION_SCHEMA.extend({ - cv.Optional(CONF_STATE, default=True): True, -})) - - -@automation.register_action('light.turn_off', LightControlAction, LIGHT_TURN_OFF_ACTION_SCHEMA) -@automation.register_action('light.turn_on', LightControlAction, LIGHT_TURN_ON_ACTION_SCHEMA) -@automation.register_action('light.control', LightControlAction, LIGHT_CONTROL_ACTION_SCHEMA) +LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(LightState), + cv.Optional(CONF_STATE): cv.templatable(cv.boolean), + cv.Exclusive(CONF_TRANSITION_LENGTH, "transformer"): cv.templatable( + cv.positive_time_period_milliseconds + ), + cv.Exclusive(CONF_FLASH_LENGTH, "transformer"): cv.templatable( + cv.positive_time_period_milliseconds + ), + cv.Exclusive(CONF_EFFECT, "transformer"): cv.templatable(cv.string), + cv.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage), + cv.Optional(CONF_RED): cv.templatable(cv.percentage), + cv.Optional(CONF_GREEN): cv.templatable(cv.percentage), + cv.Optional(CONF_BLUE): cv.templatable(cv.percentage), + cv.Optional(CONF_WHITE): cv.templatable(cv.percentage), + cv.Optional(CONF_COLOR_TEMPERATURE): cv.templatable(cv.color_temperature), + } +) +LIGHT_TURN_OFF_ACTION_SCHEMA = automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(LightState), + cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable( + cv.positive_time_period_milliseconds + ), + cv.Optional(CONF_STATE, default=False): False, + } +) +LIGHT_TURN_ON_ACTION_SCHEMA = automation.maybe_simple_id( + LIGHT_CONTROL_ACTION_SCHEMA.extend( + { + cv.Optional(CONF_STATE, default=True): True, + } + ) +) + + +@automation.register_action( + "light.turn_off", LightControlAction, LIGHT_TURN_OFF_ACTION_SCHEMA +) +@automation.register_action( + "light.turn_on", LightControlAction, LIGHT_TURN_ON_ACTION_SCHEMA +) +@automation.register_action( + "light.control", LightControlAction, LIGHT_CONTROL_ACTION_SCHEMA +) def light_control_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -56,7 +104,9 @@ def light_control_to_code(config, action_id, template_arg, args): template_ = yield cg.templatable(config[CONF_STATE], args, bool) cg.add(var.set_state(template_)) if CONF_TRANSITION_LENGTH in config: - template_ = yield cg.templatable(config[CONF_TRANSITION_LENGTH], args, cg.uint32) + template_ = yield cg.templatable( + config[CONF_TRANSITION_LENGTH], args, cg.uint32 + ) cg.add(var.set_transition_length(template_)) if CONF_FLASH_LENGTH in config: template_ = yield cg.templatable(config[CONF_FLASH_LENGTH], args, cg.uint32) @@ -85,16 +135,23 @@ def light_control_to_code(config, action_id, template_arg, args): yield var -CONF_RELATIVE_BRIGHTNESS = 'relative_brightness' -LIGHT_DIM_RELATIVE_ACTION_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.use_id(LightState), - cv.Required(CONF_RELATIVE_BRIGHTNESS): cv.templatable(cv.possibly_negative_percentage), - cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds), -}) +CONF_RELATIVE_BRIGHTNESS = "relative_brightness" +LIGHT_DIM_RELATIVE_ACTION_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(LightState), + cv.Required(CONF_RELATIVE_BRIGHTNESS): cv.templatable( + cv.possibly_negative_percentage + ), + cv.Optional(CONF_TRANSITION_LENGTH): cv.templatable( + cv.positive_time_period_milliseconds + ), + } +) -@automation.register_action('light.dim_relative', DimRelativeAction, - LIGHT_DIM_RELATIVE_ACTION_SCHEMA) +@automation.register_action( + "light.dim_relative", DimRelativeAction, LIGHT_DIM_RELATIVE_ACTION_SCHEMA +) def light_dim_relative_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -106,19 +163,22 @@ def light_dim_relative_to_code(config, action_id, template_arg, args): yield var -LIGHT_ADDRESSABLE_SET_ACTION_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.use_id(AddressableLightState), - cv.Optional(CONF_RANGE_FROM): cv.templatable(cv.positive_int), - cv.Optional(CONF_RANGE_TO): cv.templatable(cv.positive_int), - cv.Optional(CONF_RED): cv.templatable(cv.percentage), - cv.Optional(CONF_GREEN): cv.templatable(cv.percentage), - cv.Optional(CONF_BLUE): cv.templatable(cv.percentage), - cv.Optional(CONF_WHITE): cv.templatable(cv.percentage), -}) +LIGHT_ADDRESSABLE_SET_ACTION_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(AddressableLightState), + cv.Optional(CONF_RANGE_FROM): cv.templatable(cv.positive_int), + cv.Optional(CONF_RANGE_TO): cv.templatable(cv.positive_int), + cv.Optional(CONF_RED): cv.templatable(cv.percentage), + cv.Optional(CONF_GREEN): cv.templatable(cv.percentage), + cv.Optional(CONF_BLUE): cv.templatable(cv.percentage), + cv.Optional(CONF_WHITE): cv.templatable(cv.percentage), + } +) -@automation.register_action('light.addressable_set', AddressableSet, - LIGHT_ADDRESSABLE_SET_ACTION_SCHEMA) +@automation.register_action( + "light.addressable_set", AddressableSet, LIGHT_ADDRESSABLE_SET_ACTION_SCHEMA +) def light_addressable_set_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -133,28 +193,46 @@ def rgbw_to_exp(x): return int(round(x * 255)) if CONF_RED in config: - templ = yield cg.templatable(config[CONF_RED], args, cg.uint8, to_exp=rgbw_to_exp) + templ = yield cg.templatable( + config[CONF_RED], args, cg.uint8, to_exp=rgbw_to_exp + ) cg.add(var.set_red(templ)) if CONF_GREEN in config: - templ = yield cg.templatable(config[CONF_GREEN], args, cg.uint8, to_exp=rgbw_to_exp) + templ = yield cg.templatable( + config[CONF_GREEN], args, cg.uint8, to_exp=rgbw_to_exp + ) cg.add(var.set_green(templ)) if CONF_BLUE in config: - templ = yield cg.templatable(config[CONF_BLUE], args, cg.uint8, to_exp=rgbw_to_exp) + templ = yield cg.templatable( + config[CONF_BLUE], args, cg.uint8, to_exp=rgbw_to_exp + ) cg.add(var.set_blue(templ)) if CONF_WHITE in config: - templ = yield cg.templatable(config[CONF_WHITE], args, cg.uint8, to_exp=rgbw_to_exp) + templ = yield cg.templatable( + config[CONF_WHITE], args, cg.uint8, to_exp=rgbw_to_exp + ) cg.add(var.set_white(templ)) yield var -@automation.register_condition('light.is_on', LightIsOnCondition, - automation.maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(LightState), - })) -@automation.register_condition('light.is_off', LightIsOffCondition, - automation.maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(LightState), - })) +@automation.register_condition( + "light.is_on", + LightIsOnCondition, + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(LightState), + } + ), +) +@automation.register_condition( + "light.is_off", + LightIsOffCondition, + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(LightState), + } + ), +) def light_is_on_off_to_code(config, condition_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(condition_id, template_arg, paren) diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index d8c709b8ad67..9f017de98bb4 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -1,38 +1,70 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_NAME, CONF_LAMBDA, CONF_UPDATE_INTERVAL, CONF_TRANSITION_LENGTH, \ - CONF_COLORS, CONF_STATE, CONF_DURATION, CONF_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, \ - CONF_WHITE, CONF_ALPHA, CONF_INTENSITY, CONF_SPEED, CONF_WIDTH, CONF_NUM_LEDS, CONF_RANDOM, \ - CONF_SEQUENCE + +from esphome.const import ( + CONF_NAME, + CONF_LAMBDA, + CONF_UPDATE_INTERVAL, + CONF_TRANSITION_LENGTH, + CONF_COLORS, + CONF_STATE, + CONF_DURATION, + CONF_BRIGHTNESS, + CONF_RED, + CONF_GREEN, + CONF_BLUE, + CONF_WHITE, + CONF_ALPHA, + CONF_INTENSITY, + CONF_SPEED, + CONF_WIDTH, + CONF_NUM_LEDS, + CONF_RANDOM, + CONF_SEQUENCE, +) from esphome.util import Registry -from .types import LambdaLightEffect, RandomLightEffect, StrobeLightEffect, \ - StrobeLightEffectColor, LightColorValues, AddressableLightRef, AddressableLambdaLightEffect, \ - FlickerLightEffect, AddressableRainbowLightEffect, AddressableColorWipeEffect, \ - AddressableColorWipeEffectColor, AddressableScanEffect, AddressableTwinkleEffect, \ - AddressableRandomTwinkleEffect, AddressableFireworksEffect, AddressableFlickerEffect, \ - AutomationLightEffect, ESPColor - -CONF_ADD_LED_INTERVAL = 'add_led_interval' -CONF_REVERSE = 'reverse' -CONF_MOVE_INTERVAL = 'move_interval' -CONF_SCAN_WIDTH = 'scan_width' -CONF_TWINKLE_PROBABILITY = 'twinkle_probability' -CONF_PROGRESS_INTERVAL = 'progress_interval' -CONF_SPARK_PROBABILITY = 'spark_probability' -CONF_USE_RANDOM_COLOR = 'use_random_color' -CONF_FADE_OUT_RATE = 'fade_out_rate' -CONF_STROBE = 'strobe' -CONF_FLICKER = 'flicker' -CONF_ADDRESSABLE_LAMBDA = 'addressable_lambda' -CONF_ADDRESSABLE_RAINBOW = 'addressable_rainbow' -CONF_ADDRESSABLE_COLOR_WIPE = 'addressable_color_wipe' -CONF_ADDRESSABLE_SCAN = 'addressable_scan' -CONF_ADDRESSABLE_TWINKLE = 'addressable_twinkle' -CONF_ADDRESSABLE_RANDOM_TWINKLE = 'addressable_random_twinkle' -CONF_ADDRESSABLE_FIREWORKS = 'addressable_fireworks' -CONF_ADDRESSABLE_FLICKER = 'addressable_flicker' -CONF_AUTOMATION = 'automation' +from .types import ( + LambdaLightEffect, + RandomLightEffect, + StrobeLightEffect, + StrobeLightEffectColor, + LightColorValues, + AddressableLightRef, + AddressableLambdaLightEffect, + FlickerLightEffect, + AddressableRainbowLightEffect, + AddressableColorWipeEffect, + AddressableColorWipeEffectColor, + AddressableScanEffect, + AddressableTwinkleEffect, + AddressableRandomTwinkleEffect, + AddressableFireworksEffect, + AddressableFlickerEffect, + AutomationLightEffect, + Color, +) + +CONF_ADD_LED_INTERVAL = "add_led_interval" +CONF_REVERSE = "reverse" +CONF_MOVE_INTERVAL = "move_interval" +CONF_SCAN_WIDTH = "scan_width" +CONF_TWINKLE_PROBABILITY = "twinkle_probability" +CONF_PROGRESS_INTERVAL = "progress_interval" +CONF_SPARK_PROBABILITY = "spark_probability" +CONF_USE_RANDOM_COLOR = "use_random_color" +CONF_FADE_OUT_RATE = "fade_out_rate" +CONF_STROBE = "strobe" +CONF_FLICKER = "flicker" +CONF_ADDRESSABLE_LAMBDA = "addressable_lambda" +CONF_ADDRESSABLE_RAINBOW = "addressable_rainbow" +CONF_ADDRESSABLE_COLOR_WIPE = "addressable_color_wipe" +CONF_ADDRESSABLE_SCAN = "addressable_scan" +CONF_ADDRESSABLE_TWINKLE = "addressable_twinkle" +CONF_ADDRESSABLE_RANDOM_TWINKLE = "addressable_random_twinkle" +CONF_ADDRESSABLE_FIREWORKS = "addressable_fireworks" +CONF_ADDRESSABLE_FLICKER = "addressable_flicker" +CONF_AUTOMATION = "automation" BINARY_EFFECTS = [] MONOCHROMATIC_EFFECTS = [] @@ -43,9 +75,11 @@ def register_effect(name, effect_type, default_name, schema, *extra_validators): - schema = cv.Schema(schema).extend({ - cv.Optional(CONF_NAME, default=default_name): cv.string_strict, - }) + schema = cv.Schema(schema).extend( + { + cv.Optional(CONF_NAME, default=default_name): cv.string_strict, + } + ) validator = cv.All(schema, *extra_validators) return EFFECTS_REGISTRY.register(name, effect_type, validator) @@ -60,7 +94,9 @@ def register_binary_effect(name, effect_type, default_name, schema, *extra_valid return register_effect(name, effect_type, default_name, schema, *extra_validators) -def register_monochromatic_effect(name, effect_type, default_name, schema, *extra_validators): +def register_monochromatic_effect( + name, effect_type, default_name, schema, *extra_validators +): # monochromatic effect can be used for all lights expect binary MONOCHROMATIC_EFFECTS.append(name) RGB_EFFECTS.append(name) @@ -77,36 +113,58 @@ def register_rgb_effect(name, effect_type, default_name, schema, *extra_validato return register_effect(name, effect_type, default_name, schema, *extra_validators) -def register_addressable_effect(name, effect_type, default_name, schema, *extra_validators): +def register_addressable_effect( + name, effect_type, default_name, schema, *extra_validators +): # addressable effect can be used only in addressable ADDRESSABLE_EFFECTS.append(name) return register_effect(name, effect_type, default_name, schema, *extra_validators) -@register_binary_effect('lambda', LambdaLightEffect, "Lambda", { - cv.Required(CONF_LAMBDA): cv.lambda_, - cv.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.update_interval, -}) +@register_binary_effect( + "lambda", + LambdaLightEffect, + "Lambda", + { + cv.Required(CONF_LAMBDA): cv.lambda_, + cv.Optional(CONF_UPDATE_INTERVAL, default="0ms"): cv.update_interval, + }, +) def lambda_effect_to_code(config, effect_id): lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.void) - yield cg.new_Pvariable(effect_id, config[CONF_NAME], lambda_, - config[CONF_UPDATE_INTERVAL]) - - -@register_binary_effect('automation', AutomationLightEffect, "Automation", { - cv.Required(CONF_SEQUENCE): automation.validate_automation(single=True), -}) + yield cg.new_Pvariable( + effect_id, config[CONF_NAME], lambda_, config[CONF_UPDATE_INTERVAL] + ) + + +@register_binary_effect( + "automation", + AutomationLightEffect, + "Automation", + { + cv.Required(CONF_SEQUENCE): automation.validate_automation(single=True), + }, +) def automation_effect_to_code(config, effect_id): var = yield cg.new_Pvariable(effect_id, config[CONF_NAME]) yield automation.build_automation(var.get_trig(), [], config[CONF_SEQUENCE]) yield var -@register_rgb_effect('random', RandomLightEffect, "Random", { - cv.Optional(CONF_TRANSITION_LENGTH, default='7.5s'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_UPDATE_INTERVAL, default='10s'): cv.positive_time_period_milliseconds, -}) +@register_rgb_effect( + "random", + RandomLightEffect, + "Random", + { + cv.Optional( + CONF_TRANSITION_LENGTH, default="7.5s" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_UPDATE_INTERVAL, default="10s" + ): cv.positive_time_period_milliseconds, + }, +) def random_effect_to_code(config, effect_id): effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH])) @@ -114,40 +172,79 @@ def random_effect_to_code(config, effect_id): yield effect -@register_binary_effect('strobe', StrobeLightEffect, "Strobe", { - cv.Optional(CONF_COLORS, default=[ - {CONF_STATE: True, CONF_DURATION: '0.5s'}, - {CONF_STATE: False, CONF_DURATION: '0.5s'}, - ]): cv.All(cv.ensure_list(cv.Schema({ - cv.Optional(CONF_STATE, default=True): cv.boolean, - cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, - cv.Optional(CONF_RED, default=1.0): cv.percentage, - cv.Optional(CONF_GREEN, default=1.0): cv.percentage, - cv.Optional(CONF_BLUE, default=1.0): cv.percentage, - cv.Optional(CONF_WHITE, default=1.0): cv.percentage, - cv.Required(CONF_DURATION): cv.positive_time_period_milliseconds, - }), cv.has_at_least_one_key(CONF_STATE, CONF_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, - CONF_WHITE)), cv.Length(min=2)), -}) +@register_binary_effect( + "strobe", + StrobeLightEffect, + "Strobe", + { + cv.Optional( + CONF_COLORS, + default=[ + {CONF_STATE: True, CONF_DURATION: "0.5s"}, + {CONF_STATE: False, CONF_DURATION: "0.5s"}, + ], + ): cv.All( + cv.ensure_list( + cv.Schema( + { + cv.Optional(CONF_STATE, default=True): cv.boolean, + cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_RED, default=1.0): cv.percentage, + cv.Optional(CONF_GREEN, default=1.0): cv.percentage, + cv.Optional(CONF_BLUE, default=1.0): cv.percentage, + cv.Optional(CONF_WHITE, default=1.0): cv.percentage, + cv.Required( + CONF_DURATION + ): cv.positive_time_period_milliseconds, + } + ), + cv.has_at_least_one_key( + CONF_STATE, + CONF_BRIGHTNESS, + CONF_RED, + CONF_GREEN, + CONF_BLUE, + CONF_WHITE, + ), + ), + cv.Length(min=2), + ), + }, +) def strobe_effect_to_code(config, effect_id): var = cg.new_Pvariable(effect_id, config[CONF_NAME]) colors = [] for color in config.get(CONF_COLORS, []): - colors.append(cg.StructInitializer( - StrobeLightEffectColor, - ('color', LightColorValues(color[CONF_STATE], color[CONF_BRIGHTNESS], - color[CONF_RED], color[CONF_GREEN], color[CONF_BLUE], - color[CONF_WHITE])), - ('duration', color[CONF_DURATION]), - )) + colors.append( + cg.StructInitializer( + StrobeLightEffectColor, + ( + "color", + LightColorValues( + color[CONF_STATE], + color[CONF_BRIGHTNESS], + color[CONF_RED], + color[CONF_GREEN], + color[CONF_BLUE], + color[CONF_WHITE], + ), + ), + ("duration", color[CONF_DURATION]), + ) + ) cg.add(var.set_colors(colors)) yield var -@register_monochromatic_effect('flicker', FlickerLightEffect, "Flicker", { - cv.Optional(CONF_ALPHA, default=0.95): cv.percentage, - cv.Optional(CONF_INTENSITY, default=0.015): cv.percentage, -}) +@register_monochromatic_effect( + "flicker", + FlickerLightEffect, + "Flicker", + { + cv.Optional(CONF_ALPHA, default=0.95): cv.percentage, + cv.Optional(CONF_INTENSITY, default=0.015): cv.percentage, + }, +) def flicker_effect_to_code(config, effect_id): var = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(var.set_alpha(config[CONF_ALPHA])) @@ -156,23 +253,38 @@ def flicker_effect_to_code(config, effect_id): @register_addressable_effect( - 'addressable_lambda', AddressableLambdaLightEffect, "Addressable Lambda", { + "addressable_lambda", + AddressableLambdaLightEffect, + "Addressable Lambda", + { cv.Required(CONF_LAMBDA): cv.lambda_, - cv.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.positive_time_period_milliseconds, - } + cv.Optional( + CONF_UPDATE_INTERVAL, default="0ms" + ): cv.positive_time_period_milliseconds, + }, ) def addressable_lambda_effect_to_code(config, effect_id): - args = [(AddressableLightRef, 'it'), (ESPColor, 'current_color'), (bool, 'initial_run')] + args = [ + (AddressableLightRef, "it"), + (Color, "current_color"), + (bool, "initial_run"), + ] lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], args, return_type=cg.void) - var = cg.new_Pvariable(effect_id, config[CONF_NAME], lambda_, - config[CONF_UPDATE_INTERVAL]) + var = cg.new_Pvariable( + effect_id, config[CONF_NAME], lambda_, config[CONF_UPDATE_INTERVAL] + ) yield var -@register_addressable_effect('addressable_rainbow', AddressableRainbowLightEffect, "Rainbow", { - cv.Optional(CONF_SPEED, default=10): cv.uint32_t, - cv.Optional(CONF_WIDTH, default=50): cv.uint32_t, -}) +@register_addressable_effect( + "addressable_rainbow", + AddressableRainbowLightEffect, + "Rainbow", + { + cv.Optional(CONF_SPEED, default=10): cv.uint32_t, + cv.Optional(CONF_WIDTH, default=50): cv.uint32_t, + }, +) def addressable_rainbow_effect_to_code(config, effect_id): var = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(var.set_speed(config[CONF_SPEED])) @@ -180,41 +292,61 @@ def addressable_rainbow_effect_to_code(config, effect_id): yield var -@register_addressable_effect('addressable_color_wipe', AddressableColorWipeEffect, "Color Wipe", { - cv.Optional(CONF_COLORS, default=[{CONF_NUM_LEDS: 1, CONF_RANDOM: True}]): cv.ensure_list({ - cv.Optional(CONF_RED, default=1.0): cv.percentage, - cv.Optional(CONF_GREEN, default=1.0): cv.percentage, - cv.Optional(CONF_BLUE, default=1.0): cv.percentage, - cv.Optional(CONF_WHITE, default=1.0): cv.percentage, - cv.Optional(CONF_RANDOM, default=False): cv.boolean, - cv.Required(CONF_NUM_LEDS): cv.All(cv.uint32_t, cv.Range(min=1)), - }), - cv.Optional(CONF_ADD_LED_INTERVAL, default='0.1s'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_REVERSE, default=False): cv.boolean, -}) +@register_addressable_effect( + "addressable_color_wipe", + AddressableColorWipeEffect, + "Color Wipe", + { + cv.Optional( + CONF_COLORS, default=[{CONF_NUM_LEDS: 1, CONF_RANDOM: True}] + ): cv.ensure_list( + { + cv.Optional(CONF_RED, default=1.0): cv.percentage, + cv.Optional(CONF_GREEN, default=1.0): cv.percentage, + cv.Optional(CONF_BLUE, default=1.0): cv.percentage, + cv.Optional(CONF_WHITE, default=1.0): cv.percentage, + cv.Optional(CONF_RANDOM, default=False): cv.boolean, + cv.Required(CONF_NUM_LEDS): cv.All(cv.uint32_t, cv.Range(min=1)), + } + ), + cv.Optional( + CONF_ADD_LED_INTERVAL, default="0.1s" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_REVERSE, default=False): cv.boolean, + }, +) def addressable_color_wipe_effect_to_code(config, effect_id): var = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(var.set_add_led_interval(config[CONF_ADD_LED_INTERVAL])) cg.add(var.set_reverse(config[CONF_REVERSE])) colors = [] for color in config.get(CONF_COLORS, []): - colors.append(cg.StructInitializer( - AddressableColorWipeEffectColor, - ('r', int(round(color[CONF_RED] * 255))), - ('g', int(round(color[CONF_GREEN] * 255))), - ('b', int(round(color[CONF_BLUE] * 255))), - ('w', int(round(color[CONF_WHITE] * 255))), - ('random', color[CONF_RANDOM]), - ('num_leds', color[CONF_NUM_LEDS]), - )) + colors.append( + cg.StructInitializer( + AddressableColorWipeEffectColor, + ("r", int(round(color[CONF_RED] * 255))), + ("g", int(round(color[CONF_GREEN] * 255))), + ("b", int(round(color[CONF_BLUE] * 255))), + ("w", int(round(color[CONF_WHITE] * 255))), + ("random", color[CONF_RANDOM]), + ("num_leds", color[CONF_NUM_LEDS]), + ) + ) cg.add(var.set_colors(colors)) yield var -@register_addressable_effect('addressable_scan', AddressableScanEffect, "Scan", { - cv.Optional(CONF_MOVE_INTERVAL, default='0.1s'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_SCAN_WIDTH, default=1): cv.int_range(min=1), -}) +@register_addressable_effect( + "addressable_scan", + AddressableScanEffect, + "Scan", + { + cv.Optional( + CONF_MOVE_INTERVAL, default="0.1s" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_SCAN_WIDTH, default=1): cv.int_range(min=1), + }, +) def addressable_scan_effect_to_code(config, effect_id): var = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(var.set_move_interval(config[CONF_MOVE_INTERVAL])) @@ -222,10 +354,17 @@ def addressable_scan_effect_to_code(config, effect_id): yield var -@register_addressable_effect('addressable_twinkle', AddressableTwinkleEffect, "Twinkle", { - cv.Optional(CONF_TWINKLE_PROBABILITY, default='5%'): cv.percentage, - cv.Optional(CONF_PROGRESS_INTERVAL, default='4ms'): cv.positive_time_period_milliseconds, -}) +@register_addressable_effect( + "addressable_twinkle", + AddressableTwinkleEffect, + "Twinkle", + { + cv.Optional(CONF_TWINKLE_PROBABILITY, default="5%"): cv.percentage, + cv.Optional( + CONF_PROGRESS_INTERVAL, default="4ms" + ): cv.positive_time_period_milliseconds, + }, +) def addressable_twinkle_effect_to_code(config, effect_id): var = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(var.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY])) @@ -234,10 +373,15 @@ def addressable_twinkle_effect_to_code(config, effect_id): @register_addressable_effect( - 'addressable_random_twinkle', AddressableRandomTwinkleEffect, "Random Twinkle", { - cv.Optional(CONF_TWINKLE_PROBABILITY, default='5%'): cv.percentage, - cv.Optional(CONF_PROGRESS_INTERVAL, default='32ms'): cv.positive_time_period_milliseconds, - } + "addressable_random_twinkle", + AddressableRandomTwinkleEffect, + "Random Twinkle", + { + cv.Optional(CONF_TWINKLE_PROBABILITY, default="5%"): cv.percentage, + cv.Optional( + CONF_PROGRESS_INTERVAL, default="32ms" + ): cv.positive_time_period_milliseconds, + }, ) def addressable_random_twinkle_effect_to_code(config, effect_id): var = cg.new_Pvariable(effect_id, config[CONF_NAME]) @@ -246,12 +390,19 @@ def addressable_random_twinkle_effect_to_code(config, effect_id): yield var -@register_addressable_effect('addressable_fireworks', AddressableFireworksEffect, "Fireworks", { - cv.Optional(CONF_UPDATE_INTERVAL, default='32ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_SPARK_PROBABILITY, default='10%'): cv.percentage, - cv.Optional(CONF_USE_RANDOM_COLOR, default=False): cv.boolean, - cv.Optional(CONF_FADE_OUT_RATE, default=120): cv.uint8_t, -}) +@register_addressable_effect( + "addressable_fireworks", + AddressableFireworksEffect, + "Fireworks", + { + cv.Optional( + CONF_UPDATE_INTERVAL, default="32ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_SPARK_PROBABILITY, default="10%"): cv.percentage, + cv.Optional(CONF_USE_RANDOM_COLOR, default=False): cv.boolean, + cv.Optional(CONF_FADE_OUT_RATE, default=120): cv.uint8_t, + }, +) def addressable_fireworks_effect_to_code(config, effect_id): var = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) @@ -262,10 +413,15 @@ def addressable_fireworks_effect_to_code(config, effect_id): @register_addressable_effect( - 'addressable_flicker', AddressableFlickerEffect, "Addressable Flicker", { - cv.Optional(CONF_UPDATE_INTERVAL, default='16ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_INTENSITY, default='5%'): cv.percentage, - } + "addressable_flicker", + AddressableFlickerEffect, + "Addressable Flicker", + { + cv.Optional( + CONF_UPDATE_INTERVAL, default="16ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_INTENSITY, default="5%"): cv.percentage, + }, ) def addressable_flicker_effect_to_code(config, effect_id): var = cg.new_Pvariable(effect_id, config[CONF_NAME]) @@ -276,22 +432,28 @@ def addressable_flicker_effect_to_code(config, effect_id): def validate_effects(allowed_effects): def validator(value): - value = cv.validate_registry('effect', EFFECTS_REGISTRY)(value) + value = cv.validate_registry("effect", EFFECTS_REGISTRY)(value) errors = [] names = set() for i, x in enumerate(value): key = next(it for it in x.keys()) if key not in allowed_effects: errors.append( - cv.Invalid("The effect '{}' is not allowed for this " - "light type".format(key), [i]) + cv.Invalid( + "The effect '{}' is not allowed for this " + "light type".format(key), + [i], + ) ) continue name = x[key][CONF_NAME] if name in names: errors.append( - cv.Invalid("Found the effect name '{}' twice. All effects must have " - "unique names".format(name), [i]) + cv.Invalid( + "Found the effect name '{}' twice. All effects must have " + "unique names".format(name), + [i], + ) ) continue names.add(name) diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index d32ef0214c9b..4bca266b67df 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -2,47 +2,62 @@ from esphome import automation # Base -light_ns = cg.esphome_ns.namespace('light') -LightState = light_ns.class_('LightState', cg.Nameable, cg.Component) +light_ns = cg.esphome_ns.namespace("light") +LightState = light_ns.class_("LightState", cg.Nameable, cg.Component) # Fake class for addressable lights -AddressableLightState = light_ns.class_('LightState', LightState) -LightOutput = light_ns.class_('LightOutput') -AddressableLight = light_ns.class_('AddressableLight', cg.Component) -AddressableLightRef = AddressableLight.operator('ref') +AddressableLightState = light_ns.class_("LightState", LightState) +LightOutput = light_ns.class_("LightOutput") +AddressableLight = light_ns.class_("AddressableLight", cg.Component) +AddressableLightRef = AddressableLight.operator("ref") -ESPColor = light_ns.class_('ESPColor') -LightColorValues = light_ns.class_('LightColorValues') +Color = cg.esphome_ns.class_("Color") +LightColorValues = light_ns.class_("LightColorValues") # Actions -ToggleAction = light_ns.class_('ToggleAction', automation.Action) -LightControlAction = light_ns.class_('LightControlAction', automation.Action) -DimRelativeAction = light_ns.class_('DimRelativeAction', automation.Action) -AddressableSet = light_ns.class_('AddressableSet', automation.Action) -LightIsOnCondition = light_ns.class_('LightIsOnCondition', automation.Condition) -LightIsOffCondition = light_ns.class_('LightIsOffCondition', automation.Condition) +ToggleAction = light_ns.class_("ToggleAction", automation.Action) +LightControlAction = light_ns.class_("LightControlAction", automation.Action) +DimRelativeAction = light_ns.class_("DimRelativeAction", automation.Action) +AddressableSet = light_ns.class_("AddressableSet", automation.Action) +LightIsOnCondition = light_ns.class_("LightIsOnCondition", automation.Condition) +LightIsOffCondition = light_ns.class_("LightIsOffCondition", automation.Condition) # Triggers -LightTurnOnTrigger = light_ns.class_('LightTurnOnTrigger', automation.Trigger.template()) -LightTurnOffTrigger = light_ns.class_('LightTurnOffTrigger', automation.Trigger.template()) +LightTurnOnTrigger = light_ns.class_( + "LightTurnOnTrigger", automation.Trigger.template() +) +LightTurnOffTrigger = light_ns.class_( + "LightTurnOffTrigger", automation.Trigger.template() +) # Effects -LightEffect = light_ns.class_('LightEffect') -RandomLightEffect = light_ns.class_('RandomLightEffect', LightEffect) -LambdaLightEffect = light_ns.class_('LambdaLightEffect', LightEffect) -AutomationLightEffect = light_ns.class_('AutomationLightEffect', LightEffect) -StrobeLightEffect = light_ns.class_('StrobeLightEffect', LightEffect) -StrobeLightEffectColor = light_ns.class_('StrobeLightEffectColor', LightEffect) -FlickerLightEffect = light_ns.class_('FlickerLightEffect', LightEffect) -AddressableLightEffect = light_ns.class_('AddressableLightEffect', LightEffect) -AddressableLambdaLightEffect = light_ns.class_('AddressableLambdaLightEffect', - AddressableLightEffect) -AddressableRainbowLightEffect = light_ns.class_('AddressableRainbowLightEffect', - AddressableLightEffect) -AddressableColorWipeEffect = light_ns.class_('AddressableColorWipeEffect', AddressableLightEffect) -AddressableColorWipeEffectColor = light_ns.struct('AddressableColorWipeEffectColor') -AddressableScanEffect = light_ns.class_('AddressableScanEffect', AddressableLightEffect) -AddressableTwinkleEffect = light_ns.class_('AddressableTwinkleEffect', AddressableLightEffect) -AddressableRandomTwinkleEffect = light_ns.class_('AddressableRandomTwinkleEffect', - AddressableLightEffect) -AddressableFireworksEffect = light_ns.class_('AddressableFireworksEffect', AddressableLightEffect) -AddressableFlickerEffect = light_ns.class_('AddressableFlickerEffect', AddressableLightEffect) +LightEffect = light_ns.class_("LightEffect") +RandomLightEffect = light_ns.class_("RandomLightEffect", LightEffect) +LambdaLightEffect = light_ns.class_("LambdaLightEffect", LightEffect) +AutomationLightEffect = light_ns.class_("AutomationLightEffect", LightEffect) +StrobeLightEffect = light_ns.class_("StrobeLightEffect", LightEffect) +StrobeLightEffectColor = light_ns.class_("StrobeLightEffectColor", LightEffect) +FlickerLightEffect = light_ns.class_("FlickerLightEffect", LightEffect) +AddressableLightEffect = light_ns.class_("AddressableLightEffect", LightEffect) +AddressableLambdaLightEffect = light_ns.class_( + "AddressableLambdaLightEffect", AddressableLightEffect +) +AddressableRainbowLightEffect = light_ns.class_( + "AddressableRainbowLightEffect", AddressableLightEffect +) +AddressableColorWipeEffect = light_ns.class_( + "AddressableColorWipeEffect", AddressableLightEffect +) +AddressableColorWipeEffectColor = light_ns.struct("AddressableColorWipeEffectColor") +AddressableScanEffect = light_ns.class_("AddressableScanEffect", AddressableLightEffect) +AddressableTwinkleEffect = light_ns.class_( + "AddressableTwinkleEffect", AddressableLightEffect +) +AddressableRandomTwinkleEffect = light_ns.class_( + "AddressableRandomTwinkleEffect", AddressableLightEffect +) +AddressableFireworksEffect = light_ns.class_( + "AddressableFireworksEffect", AddressableLightEffect +) +AddressableFlickerEffect = light_ns.class_( + "AddressableFlickerEffect", AddressableLightEffect +) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index c447b0eee15e..5e7383cca737 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -4,49 +4,69 @@ import esphome.config_validation as cv from esphome import automation from esphome.automation import LambdaAction -from esphome.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_HARDWARE_UART, CONF_ID, \ - CONF_LEVEL, CONF_LOGS, CONF_ON_MESSAGE, CONF_TAG, CONF_TRIGGER_ID, CONF_TX_BUFFER_SIZE +from esphome.const import ( + CONF_ARGS, + CONF_BAUD_RATE, + CONF_FORMAT, + CONF_HARDWARE_UART, + CONF_ID, + CONF_LEVEL, + CONF_LOGS, + CONF_ON_MESSAGE, + CONF_TAG, + CONF_TRIGGER_ID, + CONF_TX_BUFFER_SIZE, +) from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority -CODEOWNERS = ['@esphome/core'] -logger_ns = cg.esphome_ns.namespace('logger') +CODEOWNERS = ["@esphome/core"] +logger_ns = cg.esphome_ns.namespace("logger") LOG_LEVELS = { - 'NONE': cg.global_ns.ESPHOME_LOG_LEVEL_NONE, - 'ERROR': cg.global_ns.ESPHOME_LOG_LEVEL_ERROR, - 'WARN': cg.global_ns.ESPHOME_LOG_LEVEL_WARN, - 'INFO': cg.global_ns.ESPHOME_LOG_LEVEL_INFO, - 'DEBUG': cg.global_ns.ESPHOME_LOG_LEVEL_DEBUG, - 'VERBOSE': cg.global_ns.ESPHOME_LOG_LEVEL_VERBOSE, - 'VERY_VERBOSE': cg.global_ns.ESPHOME_LOG_LEVEL_VERY_VERBOSE, + "NONE": cg.global_ns.ESPHOME_LOG_LEVEL_NONE, + "ERROR": cg.global_ns.ESPHOME_LOG_LEVEL_ERROR, + "WARN": cg.global_ns.ESPHOME_LOG_LEVEL_WARN, + "INFO": cg.global_ns.ESPHOME_LOG_LEVEL_INFO, + "DEBUG": cg.global_ns.ESPHOME_LOG_LEVEL_DEBUG, + "VERBOSE": cg.global_ns.ESPHOME_LOG_LEVEL_VERBOSE, + "VERY_VERBOSE": cg.global_ns.ESPHOME_LOG_LEVEL_VERY_VERBOSE, } LOG_LEVEL_TO_ESP_LOG = { - 'ERROR': cg.global_ns.ESP_LOGE, - 'WARN': cg.global_ns.ESP_LOGW, - 'INFO': cg.global_ns.ESP_LOGI, - 'DEBUG': cg.global_ns.ESP_LOGD, - 'VERBOSE': cg.global_ns.ESP_LOGV, - 'VERY_VERBOSE': cg.global_ns.ESP_LOGVV, + "ERROR": cg.global_ns.ESP_LOGE, + "WARN": cg.global_ns.ESP_LOGW, + "INFO": cg.global_ns.ESP_LOGI, + "DEBUG": cg.global_ns.ESP_LOGD, + "VERBOSE": cg.global_ns.ESP_LOGV, + "VERY_VERBOSE": cg.global_ns.ESP_LOGVV, } -LOG_LEVEL_SEVERITY = ['NONE', 'ERROR', 'WARN', 'INFO', 'CONFIG', 'DEBUG', 'VERBOSE', 'VERY_VERBOSE'] +LOG_LEVEL_SEVERITY = [ + "NONE", + "ERROR", + "WARN", + "INFO", + "CONFIG", + "DEBUG", + "VERBOSE", + "VERY_VERBOSE", +] -UART_SELECTION_ESP32 = ['UART0', 'UART1', 'UART2'] +UART_SELECTION_ESP32 = ["UART0", "UART1", "UART2"] -UART_SELECTION_ESP8266 = ['UART0', 'UART0_SWAP', 'UART1'] +UART_SELECTION_ESP8266 = ["UART0", "UART0_SWAP", "UART1"] HARDWARE_UART_TO_UART_SELECTION = { - 'UART0': logger_ns.UART_SELECTION_UART0, - 'UART0_SWAP': logger_ns.UART_SELECTION_UART0_SWAP, - 'UART1': logger_ns.UART_SELECTION_UART1, - 'UART2': logger_ns.UART_SELECTION_UART2, + "UART0": logger_ns.UART_SELECTION_UART0, + "UART0_SWAP": logger_ns.UART_SELECTION_UART0_SWAP, + "UART1": logger_ns.UART_SELECTION_UART1, + "UART2": logger_ns.UART_SELECTION_UART2, } HARDWARE_UART_TO_SERIAL = { - 'UART0': cg.global_ns.Serial, - 'UART0_SWAP': cg.global_ns.Serial, - 'UART1': cg.global_ns.Serial1, - 'UART2': cg.global_ns.Serial2, + "UART0": cg.global_ns.Serial, + "UART0_SWAP": cg.global_ns.Serial, + "UART1": cg.global_ns.Serial1, + "UART2": cg.global_ns.Serial2, } is_log_level = cv.one_of(*LOG_LEVELS, upper=True) @@ -61,45 +81,59 @@ def uart_selection(value): def validate_local_no_higher_than_global(value): - global_level = value.get(CONF_LEVEL, 'DEBUG') + global_level = value.get(CONF_LEVEL, "DEBUG") for tag, level in value.get(CONF_LOGS, {}).items(): if LOG_LEVEL_SEVERITY.index(level) > LOG_LEVEL_SEVERITY.index(global_level): - raise EsphomeError("The local log level {} for {} must be less severe than the " - "global log level {}.".format(level, tag, global_level)) + raise EsphomeError( + "The local log level {} for {} must be less severe than the " + "global log level {}.".format(level, tag, global_level) + ) return value -Logger = logger_ns.class_('Logger', cg.Component) -LoggerMessageTrigger = logger_ns.class_('LoggerMessageTrigger', - automation.Trigger.template(cg.int_, cg.const_char_ptr, - cg.const_char_ptr)) - -CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = 'esp8266_store_log_strings_in_flash' -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(Logger), - cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int, - cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes, - cv.Optional(CONF_HARDWARE_UART, default='UART0'): uart_selection, - cv.Optional(CONF_LEVEL, default='DEBUG'): is_log_level, - cv.Optional(CONF_LOGS, default={}): cv.Schema({ - cv.string: is_log_level, - }), - cv.Optional(CONF_ON_MESSAGE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoggerMessageTrigger), - cv.Optional(CONF_LEVEL, default='WARN'): is_log_level, - }), - - cv.SplitDefault(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH, esp8266=True): - cv.All(cv.only_on_esp8266, cv.boolean), -}).extend(cv.COMPONENT_SCHEMA), validate_local_no_higher_than_global) +Logger = logger_ns.class_("Logger", cg.Component) +LoggerMessageTrigger = logger_ns.class_( + "LoggerMessageTrigger", + automation.Trigger.template(cg.int_, cg.const_char_ptr, cg.const_char_ptr), +) + +CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash" +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Logger), + cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int, + cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes, + cv.Optional(CONF_HARDWARE_UART, default="UART0"): uart_selection, + cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level, + cv.Optional(CONF_LOGS, default={}): cv.Schema( + { + cv.string: is_log_level, + } + ), + cv.Optional(CONF_ON_MESSAGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoggerMessageTrigger), + cv.Optional(CONF_LEVEL, default="WARN"): is_log_level, + } + ), + cv.SplitDefault( + CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH, esp8266=True + ): cv.All(cv.only_on_esp8266, cv.boolean), + } + ).extend(cv.COMPONENT_SCHEMA), + validate_local_no_higher_than_global, +) @coroutine_with_priority(90.0) def to_code(config): baud_rate = config[CONF_BAUD_RATE] - rhs = Logger.new(baud_rate, - config[CONF_TX_BUFFER_SIZE], - HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]]) + rhs = Logger.new( + baud_rate, + config[CONF_TX_BUFFER_SIZE], + HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]], + ) log = cg.Pvariable(config[CONF_ID], rhs) cg.add(log.pre_setup()) @@ -107,12 +141,12 @@ def to_code(config): cg.add(log.set_log_level(tag, LOG_LEVELS[level])) level = config[CONF_LEVEL] - cg.add_define('USE_LOGGER') + cg.add_define("USE_LOGGER") this_severity = LOG_LEVEL_SEVERITY.index(level) - cg.add_build_flag('-DESPHOME_LOG_LEVEL={}'.format(LOG_LEVELS[level])) + cg.add_build_flag("-DESPHOME_LOG_LEVEL={}".format(LOG_LEVELS[level])) - verbose_severity = LOG_LEVEL_SEVERITY.index('VERBOSE') - very_verbose_severity = LOG_LEVEL_SEVERITY.index('VERY_VERBOSE') + verbose_severity = LOG_LEVEL_SEVERITY.index("VERBOSE") + very_verbose_severity = LOG_LEVEL_SEVERITY.index("VERY_VERBOSE") is_at_least_verbose = this_severity >= verbose_severity is_at_least_very_verbose = this_severity >= very_verbose_severity has_serial_logging = baud_rate != 0 @@ -122,35 +156,42 @@ def to_code(config): cg.add_build_flag(f"-DDEBUG_ESP_PORT={debug_serial_port}") cg.add_build_flag("-DLWIP_DEBUG") DEBUG_COMPONENTS = { - 'HTTP_CLIENT', - 'HTTP_SERVER', - 'HTTP_UPDATE', - 'OTA', - 'SSL', - 'TLS_MEM', - 'UPDATER', - 'WIFI', + "HTTP_CLIENT", + "HTTP_SERVER", + "HTTP_UPDATE", + "OTA", + "SSL", + "TLS_MEM", + "UPDATER", + "WIFI", # Spams logs too much: # 'MDNS_RESPONDER', } for comp in DEBUG_COMPONENTS: cg.add_build_flag(f"-DDEBUG_ESP_{comp}") if CORE.is_esp32 and is_at_least_verbose: - cg.add_build_flag('-DCORE_DEBUG_LEVEL=5') + cg.add_build_flag("-DCORE_DEBUG_LEVEL=5") if CORE.is_esp32 and is_at_least_very_verbose: - cg.add_build_flag('-DENABLE_I2C_DEBUG_BUFFER') + cg.add_build_flag("-DENABLE_I2C_DEBUG_BUFFER") if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH): - cg.add_build_flag('-DUSE_STORE_LOG_STR_IN_FLASH') + cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH") # Register at end for safe mode yield cg.register_component(log, config) for conf in config.get(CONF_ON_MESSAGE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], log, - LOG_LEVEL_SEVERITY.index(conf[CONF_LEVEL])) - yield automation.build_automation(trigger, [(cg.int_, 'level'), - (cg.const_char_ptr, 'tag'), - (cg.const_char_ptr, 'message')], conf) + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], log, LOG_LEVEL_SEVERITY.index(conf[CONF_LEVEL]) + ) + yield automation.build_automation( + trigger, + [ + (cg.int_, "level"), + (cg.const_char_ptr, "tag"), + (cg.const_char_ptr, "message"), + ], + conf, + ) def maybe_simple_message(schema): @@ -179,18 +220,27 @@ def validate_printf(value): """ # noqa matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X) if len(matches) != len(value[CONF_ARGS]): - raise cv.Invalid("Found {} printf-patterns ({}), but {} args were given!" - "".format(len(matches), ', '.join(matches), len(value[CONF_ARGS]))) + raise cv.Invalid( + "Found {} printf-patterns ({}), but {} args were given!" + "".format(len(matches), ", ".join(matches), len(value[CONF_ARGS])) + ) return value -CONF_LOGGER_LOG = 'logger.log' -LOGGER_LOG_ACTION_SCHEMA = cv.All(maybe_simple_message({ - cv.Required(CONF_FORMAT): cv.string, - cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_), - cv.Optional(CONF_LEVEL, default="DEBUG"): cv.one_of(*LOG_LEVEL_TO_ESP_LOG, upper=True), - cv.Optional(CONF_TAG, default="main"): cv.string, -}), validate_printf) +CONF_LOGGER_LOG = "logger.log" +LOGGER_LOG_ACTION_SCHEMA = cv.All( + maybe_simple_message( + { + cv.Required(CONF_FORMAT): cv.string, + cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_), + cv.Optional(CONF_LEVEL, default="DEBUG"): cv.one_of( + *LOG_LEVEL_TO_ESP_LOG, upper=True + ), + cv.Optional(CONF_TAG, default="main"): cv.string, + } + ), + validate_printf, +) @automation.register_action(CONF_LOGGER_LOG, LambdaAction, LOGGER_LOG_ACTION_SCHEMA) diff --git a/esphome/components/max31855/sensor.py b/esphome/components/max31855/sensor.py index d1b5649b3e90..5d3fa461b14b 100644 --- a/esphome/components/max31855/sensor.py +++ b/esphome/components/max31855/sensor.py @@ -1,17 +1,32 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi -from esphome.const import CONF_ID, CONF_REFERENCE_TEMPERATURE, ICON_THERMOMETER, UNIT_CELSIUS +from esphome.const import ( + CONF_ID, + CONF_REFERENCE_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, +) -max31855_ns = cg.esphome_ns.namespace('max31855') -MAX31855Sensor = max31855_ns.class_('MAX31855Sensor', sensor.Sensor, cg.PollingComponent, - spi.SPIDevice) +max31855_ns = cg.esphome_ns.namespace("max31855") +MAX31855Sensor = max31855_ns.class_( + "MAX31855Sensor", sensor.Sensor, cg.PollingComponent, spi.SPIDevice +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.GenerateID(): cv.declare_id(MAX31855Sensor), - cv.Optional(CONF_REFERENCE_TEMPERATURE): - sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2), -}).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema()) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE) + .extend( + { + cv.GenerateID(): cv.declare_id(MAX31855Sensor), + cv.Optional(CONF_REFERENCE_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) +) def to_code(config): diff --git a/esphome/components/max31856/sensor.py b/esphome/components/max31856/sensor.py index 4e1411a2a42f..afbb637609e2 100644 --- a/esphome/components/max31856/sensor.py +++ b/esphome/components/max31856/sensor.py @@ -1,22 +1,38 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi -from esphome.const import CONF_ID, CONF_MAINS_FILTER, ICON_THERMOMETER, UNIT_CELSIUS +from esphome.const import ( + CONF_ID, + CONF_MAINS_FILTER, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, +) -max31856_ns = cg.esphome_ns.namespace('max31856') -MAX31856Sensor = max31856_ns.class_('MAX31856Sensor', sensor.Sensor, cg.PollingComponent, - spi.SPIDevice) +max31856_ns = cg.esphome_ns.namespace("max31856") +MAX31856Sensor = max31856_ns.class_( + "MAX31856Sensor", sensor.Sensor, cg.PollingComponent, spi.SPIDevice +) -MAX31865ConfigFilter = max31856_ns.enum('MAX31856ConfigFilter') +MAX31865ConfigFilter = max31856_ns.enum("MAX31856ConfigFilter") FILTER = { - '50HZ': MAX31865ConfigFilter.FILTER_50HZ, - '60HZ': MAX31865ConfigFilter.FILTER_60HZ, + "50HZ": MAX31865ConfigFilter.FILTER_50HZ, + "60HZ": MAX31865ConfigFilter.FILTER_60HZ, } -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.GenerateID(): cv.declare_id(MAX31856Sensor), - cv.Optional(CONF_MAINS_FILTER, default='60HZ'): cv.enum(FILTER, upper=True, space=''), -}).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema()) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE) + .extend( + { + cv.GenerateID(): cv.declare_id(MAX31856Sensor), + cv.Optional(CONF_MAINS_FILTER, default="60HZ"): cv.enum( + FILTER, upper=True, space="" + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) +) def to_code(config): diff --git a/esphome/components/max31865/sensor.py b/esphome/components/max31865/sensor.py index 7df36dfde411..bebc42c95ee6 100644 --- a/esphome/components/max31865/sensor.py +++ b/esphome/components/max31865/sensor.py @@ -1,26 +1,48 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi -from esphome.const import CONF_ID, CONF_MAINS_FILTER, CONF_REFERENCE_RESISTANCE, \ - CONF_RTD_NOMINAL_RESISTANCE, CONF_RTD_WIRES, ICON_THERMOMETER, UNIT_CELSIUS +from esphome.const import ( + CONF_ID, + CONF_MAINS_FILTER, + CONF_REFERENCE_RESISTANCE, + CONF_RTD_NOMINAL_RESISTANCE, + CONF_RTD_WIRES, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, +) -max31865_ns = cg.esphome_ns.namespace('max31865') -MAX31865Sensor = max31865_ns.class_('MAX31865Sensor', sensor.Sensor, cg.PollingComponent, - spi.SPIDevice) +max31865_ns = cg.esphome_ns.namespace("max31865") +MAX31865Sensor = max31865_ns.class_( + "MAX31865Sensor", sensor.Sensor, cg.PollingComponent, spi.SPIDevice +) -MAX31865ConfigFilter = max31865_ns.enum('MAX31865ConfigFilter') +MAX31865ConfigFilter = max31865_ns.enum("MAX31865ConfigFilter") FILTER = { - '50HZ': MAX31865ConfigFilter.FILTER_50HZ, - '60HZ': MAX31865ConfigFilter.FILTER_60HZ, + "50HZ": MAX31865ConfigFilter.FILTER_50HZ, + "60HZ": MAX31865ConfigFilter.FILTER_60HZ, } -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2).extend({ - cv.GenerateID(): cv.declare_id(MAX31865Sensor), - cv.Required(CONF_REFERENCE_RESISTANCE): cv.All(cv.resistance, cv.Range(min=100, max=10000)), - cv.Required(CONF_RTD_NOMINAL_RESISTANCE): cv.All(cv.resistance, cv.Range(min=100, max=1000)), - cv.Optional(CONF_MAINS_FILTER, default='60HZ'): cv.enum(FILTER, upper=True, space=''), - cv.Optional(CONF_RTD_WIRES, default=4): cv.int_range(min=2, max=4), -}).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema()) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE) + .extend( + { + cv.GenerateID(): cv.declare_id(MAX31865Sensor), + cv.Required(CONF_REFERENCE_RESISTANCE): cv.All( + cv.resistance, cv.Range(min=100, max=10000) + ), + cv.Required(CONF_RTD_NOMINAL_RESISTANCE): cv.All( + cv.resistance, cv.Range(min=100, max=1000) + ), + cv.Optional(CONF_MAINS_FILTER, default="60HZ"): cv.enum( + FILTER, upper=True, space="" + ), + cv.Optional(CONF_RTD_WIRES, default=4): cv.int_range(min=2, max=4), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) +) def to_code(config): diff --git a/esphome/components/max6675/sensor.py b/esphome/components/max6675/sensor.py index 7f0c94339935..0713654f840b 100644 --- a/esphome/components/max6675/sensor.py +++ b/esphome/components/max6675/sensor.py @@ -1,15 +1,23 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi -from esphome.const import CONF_ID, ICON_THERMOMETER, UNIT_CELSIUS +from esphome.const import CONF_ID, DEVICE_CLASS_TEMPERATURE, ICON_EMPTY, UNIT_CELSIUS -max6675_ns = cg.esphome_ns.namespace('max6675') -MAX6675Sensor = max6675_ns.class_('MAX6675Sensor', sensor.Sensor, cg.PollingComponent, - spi.SPIDevice) +max6675_ns = cg.esphome_ns.namespace("max6675") +MAX6675Sensor = max6675_ns.class_( + "MAX6675Sensor", sensor.Sensor, cg.PollingComponent, spi.SPIDevice +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.GenerateID(): cv.declare_id(MAX6675Sensor), -}).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema()) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE) + .extend( + { + cv.GenerateID(): cv.declare_id(MAX6675Sensor), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) +) def to_code(config): diff --git a/esphome/components/max7219/display.py b/esphome/components/max7219/display.py index 05b383004dbe..fba4d7ca761d 100644 --- a/esphome/components/max7219/display.py +++ b/esphome/components/max7219/display.py @@ -3,21 +3,28 @@ from esphome.components import display, spi from esphome.const import CONF_ID, CONF_INTENSITY, CONF_LAMBDA, CONF_NUM_CHIPS -DEPENDENCIES = ['spi'] - -max7219_ns = cg.esphome_ns.namespace('max7219') -MAX7219Component = max7219_ns.class_('MAX7219Component', cg.PollingComponent, spi.SPIDevice) -MAX7219ComponentRef = MAX7219Component.operator('ref') - -CONF_REVERSE_ENABLE = 'reverse_enable' - -CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(MAX7219Component), - - 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), - cv.Optional(CONF_REVERSE_ENABLE, default=False): cv.boolean, -}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema()) +DEPENDENCIES = ["spi"] + +max7219_ns = cg.esphome_ns.namespace("max7219") +MAX7219Component = max7219_ns.class_( + "MAX7219Component", cg.PollingComponent, spi.SPIDevice +) +MAX7219ComponentRef = MAX7219Component.operator("ref") + +CONF_REVERSE_ENABLE = "reverse_enable" + +CONFIG_SCHEMA = ( + display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MAX7219Component), + 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), + cv.Optional(CONF_REVERSE_ENABLE, default=False): cv.boolean, + } + ) + .extend(cv.polling_component_schema("1s")) + .extend(spi.spi_device_schema()) +) def to_code(config): @@ -31,6 +38,7 @@ def to_code(config): cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE])) if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(MAX7219ComponentRef, 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], [(MAX7219ComponentRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/max7219digit/display.py b/esphome/components/max7219digit/display.py index e9aba1328723..4863312b5a6d 100644 --- a/esphome/components/max7219digit/display.py +++ b/esphome/components/max7219digit/display.py @@ -3,45 +3,61 @@ from esphome.components import display, spi from esphome.const import CONF_ID, CONF_INTENSITY, CONF_LAMBDA, CONF_NUM_CHIPS -DEPENDENCIES = ['spi'] +CODEOWNERS = ["@rspaargaren"] +DEPENDENCIES = ["spi"] -CONF_ROTATE_CHIP = 'rotate_chip' -CONF_SCROLL_SPEED = 'scroll_speed' -CONF_SCROLL_DWELL = 'scroll_dwell' -CONF_SCROLL_DELAY = 'scroll_delay' -CONF_SCROLL_ENABLE = 'scroll_enable' -CONF_SCROLL_MODE = 'scroll_mode' -CONF_REVERSE_ENABLE = 'reverse_enable' +CONF_ROTATE_CHIP = "rotate_chip" +CONF_SCROLL_SPEED = "scroll_speed" +CONF_SCROLL_DWELL = "scroll_dwell" +CONF_SCROLL_DELAY = "scroll_delay" +CONF_SCROLL_ENABLE = "scroll_enable" +CONF_SCROLL_MODE = "scroll_mode" +CONF_REVERSE_ENABLE = "reverse_enable" SCROLL_MODES = { - 'CONTINUOUS': 0, - 'STOP': 1, + "CONTINUOUS": 0, + "STOP": 1, } CHIP_MODES = { - '0': 0, - '90': 1, - '180': 2, - '270': 3, + "0": 0, + "90": 1, + "180": 2, + "270": 3, } -max7219_ns = cg.esphome_ns.namespace('max7219digit') -MAX7219Component = max7219_ns.class_('MAX7219Component', cg.PollingComponent, spi.SPIDevice, - display.DisplayBuffer) -MAX7219ComponentRef = MAX7219Component.operator('ref') +max7219_ns = cg.esphome_ns.namespace("max7219digit") +MAX7219Component = max7219_ns.class_( + "MAX7219Component", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer +) +MAX7219ComponentRef = MAX7219Component.operator("ref") -CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(MAX7219Component), - cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255), - cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15), - cv.Optional(CONF_ROTATE_CHIP, default='0'): cv.enum(CHIP_MODES, upper=True), - cv.Optional(CONF_SCROLL_MODE, default='CONTINUOUS'): cv.enum(SCROLL_MODES, upper=True), - cv.Optional(CONF_SCROLL_ENABLE, default=True): cv.boolean, - cv.Optional(CONF_SCROLL_SPEED, default='250ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_SCROLL_DELAY, default='1000ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_SCROLL_DWELL, default='1000ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_REVERSE_ENABLE, default=False): cv.boolean, -}).extend(cv.polling_component_schema('500ms')).extend(spi.spi_device_schema(cs_pin_required=True)) +CONFIG_SCHEMA = ( + display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MAX7219Component), + cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255), + cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15), + cv.Optional(CONF_ROTATE_CHIP, default="0"): cv.enum(CHIP_MODES, upper=True), + cv.Optional(CONF_SCROLL_MODE, default="CONTINUOUS"): cv.enum( + SCROLL_MODES, upper=True + ), + cv.Optional(CONF_SCROLL_ENABLE, default=True): cv.boolean, + cv.Optional( + CONF_SCROLL_SPEED, default="250ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_SCROLL_DELAY, default="1000ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_SCROLL_DWELL, default="1000ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_REVERSE_ENABLE, default=False): cv.boolean, + } + ) + .extend(cv.polling_component_schema("500ms")) + .extend(spi.spi_device_schema(cs_pin_required=True)) +) def to_code(config): @@ -61,6 +77,7 @@ def to_code(config): cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE])) if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(MAX7219ComponentRef, 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], [(MAX7219ComponentRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/mcp23008/__init__.py b/esphome/components/mcp23008/__init__.py index 858b37b60aba..ae43e9ff0a15 100644 --- a/esphome/components/mcp23008/__init__.py +++ b/esphome/components/mcp23008/__init__.py @@ -1,53 +1,28 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import pins -from esphome.components import i2c -from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED, CONF_OPEN_DRAIN_INTERRUPT +from esphome.components import i2c, mcp23x08_base, mcp23xxx_base +from esphome.const import CONF_ID -DEPENDENCIES = ['i2c'] +AUTO_LOAD = ["mcp23x08_base"] +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] MULTI_CONF = True -mcp23008_ns = cg.esphome_ns.namespace('mcp23008') -MCP23008GPIOMode = mcp23008_ns.enum('MCP23008GPIOMode') -MCP23008_GPIO_MODES = { - 'INPUT': MCP23008GPIOMode.MCP23008_INPUT, - 'INPUT_PULLUP': MCP23008GPIOMode.MCP23008_INPUT_PULLUP, - 'OUTPUT': MCP23008GPIOMode.MCP23008_OUTPUT, -} +mcp23008_ns = cg.esphome_ns.namespace("mcp23008") -MCP23008 = mcp23008_ns.class_('MCP23008', cg.Component, i2c.I2CDevice) -MCP23008GPIOPin = mcp23008_ns.class_('MCP23008GPIOPin', cg.GPIOPin) +MCP23008 = mcp23008_ns.class_("MCP23008", mcp23x08_base.MCP23X08Base, i2c.I2CDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(MCP23008), - cv.Optional(CONF_OPEN_DRAIN_INTERRUPT, default=False): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x20)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(MCP23008), + } + ) + .extend(mcp23xxx_base.MCP23XXX_CONFIG_SCHEMA) + .extend(i2c.i2c_device_schema(0x20)) +) def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) + var = yield mcp23xxx_base.register_mcp23xxx(config) yield i2c.register_i2c_device(var, config) - cg.add(var.set_open_drain_ints(config[CONF_OPEN_DRAIN_INTERRUPT])) - - -CONF_MCP23008 = 'mcp23008' -MCP23008_OUTPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_MCP23008): cv.use_id(MCP23008), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(MCP23008_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) -MCP23008_INPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_MCP23008): cv.use_id(MCP23008), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum(MCP23008_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) - - -@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23008, - (MCP23008_OUTPUT_PIN_SCHEMA, MCP23008_INPUT_PIN_SCHEMA)) -def mcp23008_pin_to_code(config): - parent = yield cg.get_variable(config[CONF_MCP23008]) - yield MCP23008GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED]) diff --git a/esphome/components/mcp23008/mcp23008.cpp b/esphome/components/mcp23008/mcp23008.cpp index 22c73224586b..eb66bfc7d7ba 100644 --- a/esphome/components/mcp23008/mcp23008.cpp +++ b/esphome/components/mcp23008/mcp23008.cpp @@ -9,85 +9,32 @@ static const char *TAG = "mcp23008"; void MCP23008::setup() { ESP_LOGCONFIG(TAG, "Setting up MCP23008..."); uint8_t iocon; - if (!this->read_reg_(MCP23008_IOCON, &iocon)) { + if (!this->read_reg(mcp23x08_base::MCP23X08_IOCON, &iocon)) { this->mark_failed(); return; } if (this->open_drain_ints_) { // enable open-drain interrupt pins, 3.3V-safe - this->write_reg_(MCP23008_IOCON, 0x04); + this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04); } } -bool MCP23008::digital_read(uint8_t pin) { - uint8_t bit = pin % 8; - uint8_t reg_addr = MCP23008_GPIO; - uint8_t value = 0; - this->read_reg_(reg_addr, &value); - return value & (1 << bit); -} -void MCP23008::digital_write(uint8_t pin, bool value) { - uint8_t reg_addr = MCP23008_OLAT; - this->update_reg_(pin, value, reg_addr); -} -void MCP23008::pin_mode(uint8_t pin, uint8_t mode) { - uint8_t iodir = MCP23008_IODIR; - uint8_t gppu = MCP23008_GPPU; - switch (mode) { - case MCP23008_INPUT: - this->update_reg_(pin, true, iodir); - break; - case MCP23008_INPUT_PULLUP: - this->update_reg_(pin, true, iodir); - this->update_reg_(pin, true, gppu); - break; - case MCP23008_OUTPUT: - this->update_reg_(pin, false, iodir); - break; - default: - break; - } -} -float MCP23008::get_setup_priority() const { return setup_priority::HARDWARE; } -bool MCP23008::read_reg_(uint8_t reg, uint8_t *value) { + +void MCP23008::dump_config() { ESP_LOGCONFIG(TAG, "MCP23008:"); } + +bool MCP23008::read_reg(uint8_t reg, uint8_t *value) { if (this->is_failed()) return false; return this->read_byte(reg, value); } -bool MCP23008::write_reg_(uint8_t reg, uint8_t value) { + +bool MCP23008::write_reg(uint8_t reg, uint8_t value) { if (this->is_failed()) return false; return this->write_byte(reg, value); } -void MCP23008::update_reg_(uint8_t pin, bool pin_value, uint8_t reg_addr) { - uint8_t bit = pin % 8; - uint8_t reg_value = 0; - if (reg_addr == MCP23008_OLAT) { - reg_value = this->olat_; - } else { - this->read_reg_(reg_addr, ®_value); - } - - if (pin_value) - reg_value |= 1 << bit; - else - reg_value &= ~(1 << bit); - - this->write_reg_(reg_addr, reg_value); - - if (reg_addr == MCP23008_OLAT) { - this->olat_ = reg_value; - } -} - -MCP23008GPIOPin::MCP23008GPIOPin(MCP23008 *parent, uint8_t pin, uint8_t mode, bool inverted) - : GPIOPin(pin, mode, inverted), parent_(parent) {} -void MCP23008GPIOPin::setup() { this->pin_mode(this->mode_); } -void MCP23008GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } -bool MCP23008GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void MCP23008GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } } // namespace mcp23008 } // namespace esphome diff --git a/esphome/components/mcp23008/mcp23008.h b/esphome/components/mcp23008/mcp23008.h index e30a924dde71..42c8e497fa4f 100644 --- a/esphome/components/mcp23008/mcp23008.h +++ b/esphome/components/mcp23008/mcp23008.h @@ -1,71 +1,23 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/components/mcp23x08_base/mcp23x08_base.h" #include "esphome/core/esphal.h" #include "esphome/components/i2c/i2c.h" namespace esphome { namespace mcp23008 { -/// Modes for MCP23008 pins -enum MCP23008GPIOMode : uint8_t { - MCP23008_INPUT = INPUT, // 0x00 - MCP23008_INPUT_PULLUP = INPUT_PULLUP, // 0x02 - MCP23008_OUTPUT = OUTPUT // 0x01 -}; - -enum MCP23008GPIORegisters { - // A side - MCP23008_IODIR = 0x00, - MCP23008_IPOL = 0x01, - MCP23008_GPINTEN = 0x02, - MCP23008_DEFVAL = 0x03, - MCP23008_INTCON = 0x04, - MCP23008_IOCON = 0x05, - MCP23008_GPPU = 0x06, - MCP23008_INTF = 0x07, - MCP23008_INTCAP = 0x08, - MCP23008_GPIO = 0x09, - MCP23008_OLAT = 0x0A, -}; - -class MCP23008 : public Component, public i2c::I2CDevice { +class MCP23008 : public mcp23x08_base::MCP23X08Base, public i2c::I2CDevice { public: MCP23008() = default; void setup() override; - - bool digital_read(uint8_t pin); - void digital_write(uint8_t pin, bool value); - void pin_mode(uint8_t pin, uint8_t mode); - - void set_open_drain_ints(const bool value) { open_drain_ints_ = value; } - - float get_setup_priority() const override; - - protected: - // read a given register - bool read_reg_(uint8_t reg, uint8_t *value); - // write a value to a given register - bool write_reg_(uint8_t reg, uint8_t value); - // update registers with given pin value. - void update_reg_(uint8_t pin, bool pin_value, uint8_t reg_a); - - uint8_t olat_{0x00}; - bool open_drain_ints_; -}; - -class MCP23008GPIOPin : public GPIOPin { - public: - MCP23008GPIOPin(MCP23008 *parent, uint8_t pin, uint8_t mode, bool inverted = false); - - void setup() override; - void pin_mode(uint8_t mode) override; - bool digital_read() override; - void digital_write(bool value) override; + void dump_config() override; protected: - MCP23008 *parent_; + bool read_reg(uint8_t reg, uint8_t *value) override; + bool write_reg(uint8_t reg, uint8_t value) override; }; } // namespace mcp23008 diff --git a/esphome/components/mcp23016/__init__.py b/esphome/components/mcp23016/__init__.py index 93c3d3843ce9..2aa29c9cde59 100644 --- a/esphome/components/mcp23016/__init__.py +++ b/esphome/components/mcp23016/__init__.py @@ -4,22 +4,28 @@ from esphome.components import i2c from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] MULTI_CONF = True -mcp23016_ns = cg.esphome_ns.namespace('mcp23016') -MCP23016GPIOMode = mcp23016_ns.enum('MCP23016GPIOMode') +mcp23016_ns = cg.esphome_ns.namespace("mcp23016") +MCP23016GPIOMode = mcp23016_ns.enum("MCP23016GPIOMode") MCP23016_GPIO_MODES = { - 'INPUT': MCP23016GPIOMode.MCP23016_INPUT, - 'OUTPUT': MCP23016GPIOMode.MCP23016_OUTPUT, + "INPUT": MCP23016GPIOMode.MCP23016_INPUT, + "OUTPUT": MCP23016GPIOMode.MCP23016_OUTPUT, } -MCP23016 = mcp23016_ns.class_('MCP23016', cg.Component, i2c.I2CDevice) -MCP23016GPIOPin = mcp23016_ns.class_('MCP23016GPIOPin', cg.GPIOPin) +MCP23016 = mcp23016_ns.class_("MCP23016", cg.Component, i2c.I2CDevice) +MCP23016GPIOPin = mcp23016_ns.class_("MCP23016GPIOPin", cg.GPIOPin) -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(MCP23016), -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x20)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(MCP23016), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x20)) +) def to_code(config): @@ -28,23 +34,34 @@ def to_code(config): yield i2c.register_i2c_device(var, config) -CONF_MCP23016 = 'mcp23016' -MCP23016_OUTPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_MCP23016): cv.use_id(MCP23016), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(MCP23016_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) -MCP23016_INPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_MCP23016): cv.use_id(MCP23016), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum(MCP23016_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) +CONF_MCP23016 = "mcp23016" +MCP23016_OUTPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_MCP23016): cv.use_id(MCP23016), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum( + MCP23016_GPIO_MODES, upper=True + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) +MCP23016_INPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_MCP23016): cv.use_id(MCP23016), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="INPUT"): cv.enum( + MCP23016_GPIO_MODES, upper=True + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) -@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23016, - (MCP23016_OUTPUT_PIN_SCHEMA, MCP23016_INPUT_PIN_SCHEMA)) +@pins.PIN_SCHEMA_REGISTRY.register( + CONF_MCP23016, (MCP23016_OUTPUT_PIN_SCHEMA, MCP23016_INPUT_PIN_SCHEMA) +) def mcp23016_pin_to_code(config): parent = yield cg.get_variable(config[CONF_MCP23016]) - yield MCP23016GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED]) + yield MCP23016GPIOPin.new( + parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED] + ) diff --git a/esphome/components/mcp23017/__init__.py b/esphome/components/mcp23017/__init__.py index 34f94b293a71..a86133e232ec 100644 --- a/esphome/components/mcp23017/__init__.py +++ b/esphome/components/mcp23017/__init__.py @@ -1,53 +1,28 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import pins -from esphome.components import i2c -from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED, CONF_OPEN_DRAIN_INTERRUPT +from esphome.components import i2c, mcp23x17_base, mcp23xxx_base +from esphome.const import CONF_ID -DEPENDENCIES = ['i2c'] +AUTO_LOAD = ["mcp23x17_base"] +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] MULTI_CONF = True -mcp23017_ns = cg.esphome_ns.namespace('mcp23017') -MCP23017GPIOMode = mcp23017_ns.enum('MCP23017GPIOMode') -MCP23017_GPIO_MODES = { - 'INPUT': MCP23017GPIOMode.MCP23017_INPUT, - 'INPUT_PULLUP': MCP23017GPIOMode.MCP23017_INPUT_PULLUP, - 'OUTPUT': MCP23017GPIOMode.MCP23017_OUTPUT, -} +mcp23017_ns = cg.esphome_ns.namespace("mcp23017") -MCP23017 = mcp23017_ns.class_('MCP23017', cg.Component, i2c.I2CDevice) -MCP23017GPIOPin = mcp23017_ns.class_('MCP23017GPIOPin', cg.GPIOPin) +MCP23017 = mcp23017_ns.class_("MCP23017", mcp23x17_base.MCP23X17Base, i2c.I2CDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(MCP23017), - cv.Optional(CONF_OPEN_DRAIN_INTERRUPT, default=False): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x20)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(MCP23017), + } + ) + .extend(mcp23xxx_base.MCP23XXX_CONFIG_SCHEMA) + .extend(i2c.i2c_device_schema(0x20)) +) def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) + var = yield mcp23xxx_base.register_mcp23xxx(config) yield i2c.register_i2c_device(var, config) - cg.add(var.set_open_drain_ints(config[CONF_OPEN_DRAIN_INTERRUPT])) - - -CONF_MCP23017 = 'mcp23017' -MCP23017_OUTPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_MCP23017): cv.use_id(MCP23017), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(MCP23017_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) -MCP23017_INPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_MCP23017): cv.use_id(MCP23017), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum(MCP23017_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) - - -@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23017, - (MCP23017_OUTPUT_PIN_SCHEMA, MCP23017_INPUT_PIN_SCHEMA)) -def mcp23017_pin_to_code(config): - parent = yield cg.get_variable(config[CONF_MCP23017]) - yield MCP23017GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED]) diff --git a/esphome/components/mcp23017/mcp23017.cpp b/esphome/components/mcp23017/mcp23017.cpp index ec972668efde..0523b14d5a2a 100644 --- a/esphome/components/mcp23017/mcp23017.cpp +++ b/esphome/components/mcp23017/mcp23017.cpp @@ -9,90 +9,32 @@ static const char *TAG = "mcp23017"; void MCP23017::setup() { ESP_LOGCONFIG(TAG, "Setting up MCP23017..."); uint8_t iocon; - if (!this->read_reg_(MCP23017_IOCONA, &iocon)) { + if (!this->read_reg(mcp23x17_base::MCP23X17_IOCONA, &iocon)) { this->mark_failed(); return; } if (this->open_drain_ints_) { // enable open-drain interrupt pins, 3.3V-safe - this->write_reg_(MCP23017_IOCONA, 0x04); - this->write_reg_(MCP23017_IOCONB, 0x04); + this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04); + this->write_reg(mcp23x17_base::MCP23X17_IOCONB, 0x04); } } -bool MCP23017::digital_read(uint8_t pin) { - uint8_t bit = pin % 8; - uint8_t reg_addr = pin < 8 ? MCP23017_GPIOA : MCP23017_GPIOB; - uint8_t value = 0; - this->read_reg_(reg_addr, &value); - return value & (1 << bit); -} -void MCP23017::digital_write(uint8_t pin, bool value) { - uint8_t reg_addr = pin < 8 ? MCP23017_OLATA : MCP23017_OLATB; - this->update_reg_(pin, value, reg_addr); -} -void MCP23017::pin_mode(uint8_t pin, uint8_t mode) { - uint8_t iodir = pin < 8 ? MCP23017_IODIRA : MCP23017_IODIRB; - uint8_t gppu = pin < 8 ? MCP23017_GPPUA : MCP23017_GPPUB; - switch (mode) { - case MCP23017_INPUT: - this->update_reg_(pin, true, iodir); - break; - case MCP23017_INPUT_PULLUP: - this->update_reg_(pin, true, iodir); - this->update_reg_(pin, true, gppu); - break; - case MCP23017_OUTPUT: - this->update_reg_(pin, false, iodir); - break; - default: - break; - } -} -float MCP23017::get_setup_priority() const { return setup_priority::IO; } -bool MCP23017::read_reg_(uint8_t reg, uint8_t *value) { + +void MCP23017::dump_config() { ESP_LOGCONFIG(TAG, "MCP23017:"); } + +bool MCP23017::read_reg(uint8_t reg, uint8_t *value) { if (this->is_failed()) return false; return this->read_byte(reg, value); } -bool MCP23017::write_reg_(uint8_t reg, uint8_t value) { +bool MCP23017::write_reg(uint8_t reg, uint8_t value) { if (this->is_failed()) return false; return this->write_byte(reg, value); } -void MCP23017::update_reg_(uint8_t pin, bool pin_value, uint8_t reg_addr) { - uint8_t bit = pin % 8; - uint8_t reg_value = 0; - if (reg_addr == MCP23017_OLATA) { - reg_value = this->olat_a_; - } else if (reg_addr == MCP23017_OLATB) { - reg_value = this->olat_b_; - } else { - this->read_reg_(reg_addr, ®_value); - } - - if (pin_value) - reg_value |= 1 << bit; - else - reg_value &= ~(1 << bit); - - this->write_reg_(reg_addr, reg_value); - - if (reg_addr == MCP23017_OLATA) { - this->olat_a_ = reg_value; - } else if (reg_addr == MCP23017_OLATB) { - this->olat_b_ = reg_value; - } -} - -MCP23017GPIOPin::MCP23017GPIOPin(MCP23017 *parent, uint8_t pin, uint8_t mode, bool inverted) - : GPIOPin(pin, mode, inverted), parent_(parent) {} -void MCP23017GPIOPin::setup() { this->pin_mode(this->mode_); } -void MCP23017GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } -bool MCP23017GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void MCP23017GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } } // namespace mcp23017 } // namespace esphome diff --git a/esphome/components/mcp23017/mcp23017.h b/esphome/components/mcp23017/mcp23017.h index 5656dcc58d22..fd9086a4925e 100644 --- a/esphome/components/mcp23017/mcp23017.h +++ b/esphome/components/mcp23017/mcp23017.h @@ -1,84 +1,23 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/components/mcp23x17_base/mcp23x17_base.h" #include "esphome/core/esphal.h" #include "esphome/components/i2c/i2c.h" namespace esphome { namespace mcp23017 { -/// Modes for MCP23017 pins -enum MCP23017GPIOMode : uint8_t { - MCP23017_INPUT = INPUT, // 0x00 - MCP23017_INPUT_PULLUP = INPUT_PULLUP, // 0x02 - MCP23017_OUTPUT = OUTPUT // 0x01 -}; - -enum MCP23017GPIORegisters { - // A side - MCP23017_IODIRA = 0x00, - MCP23017_IPOLA = 0x02, - MCP23017_GPINTENA = 0x04, - MCP23017_DEFVALA = 0x06, - MCP23017_INTCONA = 0x08, - MCP23017_IOCONA = 0x0A, - MCP23017_GPPUA = 0x0C, - MCP23017_INTFA = 0x0E, - MCP23017_INTCAPA = 0x10, - MCP23017_GPIOA = 0x12, - MCP23017_OLATA = 0x14, - // B side - MCP23017_IODIRB = 0x01, - MCP23017_IPOLB = 0x03, - MCP23017_GPINTENB = 0x05, - MCP23017_DEFVALB = 0x07, - MCP23017_INTCONB = 0x09, - MCP23017_IOCONB = 0x0B, - MCP23017_GPPUB = 0x0D, - MCP23017_INTFB = 0x0F, - MCP23017_INTCAPB = 0x11, - MCP23017_GPIOB = 0x13, - MCP23017_OLATB = 0x15, -}; - -class MCP23017 : public Component, public i2c::I2CDevice { +class MCP23017 : public mcp23x17_base::MCP23X17Base, public i2c::I2CDevice { public: MCP23017() = default; void setup() override; - - bool digital_read(uint8_t pin); - void digital_write(uint8_t pin, bool value); - void pin_mode(uint8_t pin, uint8_t mode); - - void set_open_drain_ints(const bool value) { open_drain_ints_ = value; } - - float get_setup_priority() const override; - - protected: - // read a given register - bool read_reg_(uint8_t reg, uint8_t *value); - // write a value to a given register - bool write_reg_(uint8_t reg, uint8_t value); - // update registers with given pin value. - void update_reg_(uint8_t pin, bool pin_value, uint8_t reg_a); - - uint8_t olat_a_{0x00}; - uint8_t olat_b_{0x00}; - bool open_drain_ints_; -}; - -class MCP23017GPIOPin : public GPIOPin { - public: - MCP23017GPIOPin(MCP23017 *parent, uint8_t pin, uint8_t mode, bool inverted = false); - - void setup() override; - void pin_mode(uint8_t mode) override; - bool digital_read() override; - void digital_write(bool value) override; + void dump_config() override; protected: - MCP23017 *parent_; + bool read_reg(uint8_t reg, uint8_t *value) override; + bool write_reg(uint8_t reg, uint8_t value) override; }; } // namespace mcp23017 diff --git a/esphome/components/mcp23s08/__init__.py b/esphome/components/mcp23s08/__init__.py index 1440f74f562b..cfeb65de9b6f 100644 --- a/esphome/components/mcp23s08/__init__.py +++ b/esphome/components/mcp23s08/__init__.py @@ -1,58 +1,32 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import pins -from esphome.components import spi -from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED +from esphome.components import spi, mcp23x08_base, mcp23xxx_base +from esphome.const import CONF_ID -CODEOWNERS = ['@SenexCrenshaw'] - -DEPENDENCIES = ['spi'] +AUTO_LOAD = ["mcp23x08_base"] +CODEOWNERS = ["@SenexCrenshaw", "@jesserockz"] +DEPENDENCIES = ["spi"] MULTI_CONF = True CONF_DEVICEADDRESS = "deviceaddress" -mcp23S08_ns = cg.esphome_ns.namespace('mcp23s08') -mcp23S08GPIOMode = mcp23S08_ns.enum('MCP23S08GPIOMode') -mcp23S08_GPIO_MODES = { - 'INPUT': mcp23S08GPIOMode.MCP23S08_INPUT, - 'INPUT_PULLUP': mcp23S08GPIOMode.MCP23S08_INPUT_PULLUP, - 'OUTPUT': mcp23S08GPIOMode.MCP23S08_OUTPUT, -} +mcp23S08_ns = cg.esphome_ns.namespace("mcp23s08") -mcp23S08 = mcp23S08_ns.class_('MCP23S08', cg.Component, spi.SPIDevice) -mcp23S08GPIOPin = mcp23S08_ns.class_('MCP23S08GPIOPin', cg.GPIOPin) +mcp23S08 = mcp23S08_ns.class_("MCP23S08", mcp23x08_base.MCP23X08Base, spi.SPIDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(mcp23S08), - cv.Optional(CONF_DEVICEADDRESS, default=0): cv.uint8_t, -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema()) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(mcp23S08), + cv.Optional(CONF_DEVICEADDRESS, default=0): cv.uint8_t, + } + ) + .extend(mcp23xxx_base.MCP23XXX_CONFIG_SCHEMA) + .extend(spi.spi_device_schema()) +) def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = yield mcp23xxx_base.register_mcp23xxx(config) cg.add(var.set_device_address(config[CONF_DEVICEADDRESS])) - yield cg.register_component(var, config) yield spi.register_spi_device(var, config) - - -CONF_MCP23S08 = 'mcp23s08' - -mcp23S08_OUTPUT_PIN_SCHEMA = cv.Schema({ - cv.GenerateID(CONF_MCP23S08): cv.use_id(mcp23S08), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(mcp23S08_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) -mcp23S08_INPUT_PIN_SCHEMA = cv.Schema({ - cv.GenerateID(CONF_MCP23S08): cv.use_id(mcp23S08), - cv.Required(CONF_NUMBER): cv.int_range(0, 7), - cv.Optional(CONF_MODE, default="INPUT"): cv.enum(mcp23S08_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) - - -@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23S08, - (mcp23S08_OUTPUT_PIN_SCHEMA, mcp23S08_INPUT_PIN_SCHEMA)) -def mcp23S08_pin_to_code(config): - parent = yield cg.get_variable(config[CONF_MCP23S08]) - yield mcp23S08GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED]) diff --git a/esphome/components/mcp23s08/mcp23s08.cpp b/esphome/components/mcp23s08/mcp23s08.cpp index 07e1808485a6..3bb03cb225bf 100644 --- a/esphome/components/mcp23s08/mcp23s08.cpp +++ b/esphome/components/mcp23s08/mcp23s08.cpp @@ -15,14 +15,18 @@ void MCP23S08::set_device_address(uint8_t device_addr) { void MCP23S08::setup() { ESP_LOGCONFIG(TAG, "Setting up MCP23S08..."); this->spi_setup(); + this->enable(); + uint8_t cmd = 0b01000000; + this->transfer_byte(cmd); + this->transfer_byte(mcp23x08_base::MCP23X08_IOCON); + this->transfer_byte(0b00011000); // Enable HAEN pins for addressing + this->disable(); - this->transfer_byte(MCP23S08_IODIR); - this->transfer_byte(0xFF); - for (uint8_t i = 0; i < MCP23S08_OLAT; i++) { - this->transfer_byte(0x00); + if (this->open_drain_ints_) { + // enable open-drain interrupt pins, 3.3V-safe + this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04); } - this->disable(); } void MCP23S08::dump_config() { @@ -30,92 +34,24 @@ void MCP23S08::dump_config() { LOG_PIN(" CS Pin: ", this->cs_); } -float MCP23S08::get_setup_priority() const { return setup_priority::HARDWARE; } - -bool MCP23S08::digital_read(uint8_t pin) { - if (pin > 7) { - return false; - } - uint8_t bit = pin % 8; - uint8_t reg_addr = MCP23S08_GPIO; - uint8_t value = 0; - this->read_reg(reg_addr, &value); - return value & (1 << bit); -} - -void MCP23S08::digital_write(uint8_t pin, bool value) { - if (pin > 7) { - return; - } - uint8_t reg_addr = MCP23S08_OLAT; - this->update_reg(pin, value, reg_addr); -} - -void MCP23S08::pin_mode(uint8_t pin, uint8_t mode) { - uint8_t iodir = MCP23S08_IODIR; - uint8_t gppu = MCP23S08_GPPU; - switch (mode) { - case MCP23S08_INPUT: - this->update_reg(pin, true, iodir); - break; - case MCP23S08_INPUT_PULLUP: - this->update_reg(pin, true, iodir); - this->update_reg(pin, true, gppu); - break; - case MCP23S08_OUTPUT: - this->update_reg(pin, false, iodir); - break; - default: - break; - } -} - -void MCP23S08::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) { - uint8_t bit = pin % 8; - uint8_t reg_value = 0; - if (reg_addr == MCP23S08_OLAT) { - reg_value = this->olat_; - } else { - this->read_reg(reg_addr, ®_value); - } - - if (pin_value) - reg_value |= 1 << bit; - else - reg_value &= ~(1 << bit); - - this->write_reg(reg_addr, reg_value); - - if (reg_addr == MCP23S08_OLAT) { - this->olat_ = reg_value; - } -} - -bool MCP23S08::write_reg(uint8_t reg, uint8_t value) { +bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { + uint8_t data; this->enable(); - this->transfer_byte(this->device_opcode_); + this->transfer_byte(this->device_opcode_ | 1); this->transfer_byte(reg); - this->transfer_byte(value); + *value = this->transfer_byte(0); this->disable(); return true; } -bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { - uint8_t data; +bool MCP23S08::write_reg(uint8_t reg, uint8_t value) { this->enable(); - this->transfer_byte(this->device_opcode_ | 1); + this->transfer_byte(this->device_opcode_); this->transfer_byte(reg); - *value = this->transfer_byte(0); + this->transfer_byte(value); this->disable(); return true; } -MCP23S08GPIOPin::MCP23S08GPIOPin(MCP23S08 *parent, uint8_t pin, uint8_t mode, bool inverted) - : GPIOPin(pin, mode, inverted), parent_(parent) {} -void MCP23S08GPIOPin::setup() { this->pin_mode(this->mode_); } -void MCP23S08GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } -bool MCP23S08GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void MCP23S08GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } - } // namespace mcp23s08 } // namespace esphome diff --git a/esphome/components/mcp23s08/mcp23s08.h b/esphome/components/mcp23s08/mcp23s08.h index a90f89ba234b..4ca02c54fc76 100644 --- a/esphome/components/mcp23s08/mcp23s08.h +++ b/esphome/components/mcp23s08/mcp23s08.h @@ -1,35 +1,14 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/components/mcp23x08_base/mcp23x08_base.h" #include "esphome/core/esphal.h" #include "esphome/components/spi/spi.h" namespace esphome { namespace mcp23s08 { -/// Modes for MCP23S08 pins -enum MCP23S08GPIOMode : uint8_t { - MCP23S08_INPUT = INPUT, // 0x00 - MCP23S08_INPUT_PULLUP = INPUT_PULLUP, // 0x02 - MCP23S08_OUTPUT = OUTPUT // 0x01 -}; - -enum MCP23S08GPIORegisters { - // A side - MCP23S08_IODIR = 0x00, - MCP23S08_IPOL = 0x01, - MCP23S08_GPINTEN = 0x02, - MCP23S08_DEFVAL = 0x03, - MCP23S08_INTCON = 0x04, - MCP23S08_IOCON = 0x05, - MCP23S08_GPPU = 0x06, - MCP23S08_INTF = 0x07, - MCP23S08_INTCAP = 0x08, - MCP23S08_GPIO = 0x09, - MCP23S08_OLAT = 0x0A, -}; - -class MCP23S08 : public Component, +class MCP23S08 : public mcp23x08_base::MCP23X08Base, public spi::SPIDevice { public: @@ -37,37 +16,14 @@ class MCP23S08 : public Component, void setup() override; void dump_config() override; - bool digital_read(uint8_t pin); - void digital_write(uint8_t pin, bool value); - void pin_mode(uint8_t pin, uint8_t mode); void set_device_address(uint8_t device_addr); - float get_setup_priority() const override; - - // read a given register - bool read_reg(uint8_t reg, uint8_t *value); - // write a value to a given register - bool write_reg(uint8_t reg, uint8_t value); - // update registers with given pin value. - void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a); - protected: - uint8_t device_opcode_ = 0x40; - uint8_t olat_{0x00}; -}; - -class MCP23S08GPIOPin : public GPIOPin { - public: - MCP23S08GPIOPin(MCP23S08 *parent, uint8_t pin, uint8_t mode, bool inverted = false); + bool read_reg(uint8_t reg, uint8_t *value) override; + bool write_reg(uint8_t reg, uint8_t value) override; - void setup() override; - void pin_mode(uint8_t mode) override; - bool digital_read() override; - void digital_write(bool value) override; - - protected: - MCP23S08 *parent_; + uint8_t device_opcode_ = 0x40; }; } // namespace mcp23s08 diff --git a/esphome/components/mcp23s17/__init__.py b/esphome/components/mcp23s17/__init__.py index c0c06d495cdc..c38bd2617a62 100644 --- a/esphome/components/mcp23s17/__init__.py +++ b/esphome/components/mcp23s17/__init__.py @@ -1,58 +1,32 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import pins -from esphome.components import spi -from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED +from esphome.components import spi, mcp23x17_base, mcp23xxx_base +from esphome.const import CONF_ID -CODEOWNERS = ['@SenexCrenshaw'] - -DEPENDENCIES = ['spi'] +AUTO_LOAD = ["mcp23x17_base"] +CODEOWNERS = ["@SenexCrenshaw", "@jesserockz"] +DEPENDENCIES = ["spi"] MULTI_CONF = True CONF_DEVICEADDRESS = "deviceaddress" -mcp23S17_ns = cg.esphome_ns.namespace('mcp23s17') -mcp23S17GPIOMode = mcp23S17_ns.enum('MCP23S17GPIOMode') -mcp23S17_GPIO_MODES = { - 'INPUT': mcp23S17GPIOMode.MCP23S17_INPUT, - 'INPUT_PULLUP': mcp23S17GPIOMode.MCP23S17_INPUT_PULLUP, - 'OUTPUT': mcp23S17GPIOMode.MCP23S17_OUTPUT, -} +mcp23S17_ns = cg.esphome_ns.namespace("mcp23s17") -mcp23S17 = mcp23S17_ns.class_('MCP23S17', cg.Component, spi.SPIDevice) -mcp23S17GPIOPin = mcp23S17_ns.class_('MCP23S17GPIOPin', cg.GPIOPin) +mcp23S17 = mcp23S17_ns.class_("MCP23S17", mcp23x17_base.MCP23X17Base, spi.SPIDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(mcp23S17), - cv.Optional(CONF_DEVICEADDRESS, default=0): cv.uint8_t, -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema()) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(mcp23S17), + cv.Optional(CONF_DEVICEADDRESS, default=0): cv.uint8_t, + } + ) + .extend(mcp23xxx_base.MCP23XXX_CONFIG_SCHEMA) + .extend(spi.spi_device_schema()) +) def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = yield mcp23xxx_base.register_mcp23xxx(config) cg.add(var.set_device_address(config[CONF_DEVICEADDRESS])) - yield cg.register_component(var, config) yield spi.register_spi_device(var, config) - - -CONF_MCP23S17 = 'mcp23s17' - -mcp23S17_OUTPUT_PIN_SCHEMA = cv.Schema({ - cv.GenerateID(CONF_MCP23S17): cv.use_id(mcp23S17), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(mcp23S17_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) -mcp23S17_INPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_MCP23S17): cv.use_id(mcp23S17), - cv.Required(CONF_NUMBER): cv.int_range(0, 15), - cv.Optional(CONF_MODE, default="INPUT"): cv.enum(mcp23S17_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) - - -@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23S17, - (mcp23S17_OUTPUT_PIN_SCHEMA, mcp23S17_INPUT_PIN_SCHEMA)) -def mcp23S17_pin_to_code(config): - parent = yield cg.get_variable(config[CONF_MCP23S17]) - yield mcp23S17GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED]) diff --git a/esphome/components/mcp23s17/mcp23s17.cpp b/esphome/components/mcp23s17/mcp23s17.cpp index 30e4f6395344..7c2cfcd52608 100644 --- a/esphome/components/mcp23s17/mcp23s17.cpp +++ b/esphome/components/mcp23s17/mcp23s17.cpp @@ -19,17 +19,15 @@ void MCP23S17::setup() { this->enable(); uint8_t cmd = 0b01000000; this->transfer_byte(cmd); - this->transfer_byte(0x18); - this->transfer_byte(0x0A); - this->transfer_byte(this->device_opcode_); - this->transfer_byte(0); - this->transfer_byte(0xFF); - this->transfer_byte(0xFF); + this->transfer_byte(mcp23x17_base::MCP23X17_IOCONA); + this->transfer_byte(0b00011000); // Enable HAEN pins for addressing + this->disable(); - for (uint8_t i = 0; i < 20; i++) { - this->transfer_byte(0); + if (this->open_drain_ints_) { + // enable open-drain interrupt pins, 3.3V-safe + this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04); + this->write_reg(mcp23x17_base::MCP23X17_IOCONB, 0x04); } - this->disable(); } void MCP23S17::dump_config() { @@ -37,65 +35,6 @@ void MCP23S17::dump_config() { LOG_PIN(" CS Pin: ", this->cs_); } -float MCP23S17::get_setup_priority() const { return setup_priority::HARDWARE; } - -bool MCP23S17::digital_read(uint8_t pin) { - uint8_t bit = pin % 8; - uint8_t reg_addr = pin < 8 ? MCP23S17_GPIOA : MCP23S17_GPIOB; - uint8_t value = 0; - this->read_reg(reg_addr, &value); - return value & (1 << bit); -} - -void MCP23S17::digital_write(uint8_t pin, bool value) { - uint8_t reg_addr = pin < 8 ? MCP23S17_OLATA : MCP23S17_OLATB; - this->update_reg(pin, value, reg_addr); -} - -void MCP23S17::pin_mode(uint8_t pin, uint8_t mode) { - uint8_t iodir = pin < 8 ? MCP23S17_IODIRA : MCP23S17_IODIRB; - uint8_t gppu = pin < 8 ? MCP23S17_GPPUA : MCP23S17_GPPUB; - switch (mode) { - case MCP23S17_INPUT: - this->update_reg(pin, true, iodir); - break; - case MCP23S17_INPUT_PULLUP: - this->update_reg(pin, true, iodir); - this->update_reg(pin, true, gppu); - break; - case MCP23S17_OUTPUT: - this->update_reg(pin, false, iodir); - break; - default: - break; - } -} - -void MCP23S17::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) { - uint8_t bit = pin % 8; - uint8_t reg_value = 0; - if (reg_addr == MCP23S17_OLATA) { - reg_value = this->olat_a_; - } else if (reg_addr == MCP23S17_OLATB) { - reg_value = this->olat_b_; - } else { - this->read_reg(reg_addr, ®_value); - } - - if (pin_value) - reg_value |= 1 << bit; - else - reg_value &= ~(1 << bit); - - this->write_reg(reg_addr, reg_value); - - if (reg_addr == MCP23S17_OLATA) { - this->olat_a_ = reg_value; - } else if (reg_addr == MCP23S17_OLATB) { - this->olat_b_ = reg_value; - } -} - bool MCP23S17::read_reg(uint8_t reg, uint8_t *value) { this->enable(); this->transfer_byte(this->device_opcode_ | 1); @@ -115,12 +54,5 @@ bool MCP23S17::write_reg(uint8_t reg, uint8_t value) { return true; } -MCP23S17GPIOPin::MCP23S17GPIOPin(MCP23S17 *parent, uint8_t pin, uint8_t mode, bool inverted) - : GPIOPin(pin, mode, inverted), parent_(parent) {} -void MCP23S17GPIOPin::setup() { this->pin_mode(this->mode_); } -void MCP23S17GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } -bool MCP23S17GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void MCP23S17GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } - } // namespace mcp23s17 } // namespace esphome diff --git a/esphome/components/mcp23s17/mcp23s17.h b/esphome/components/mcp23s17/mcp23s17.h index 8e27ff447f19..1ced144c23ae 100644 --- a/esphome/components/mcp23s17/mcp23s17.h +++ b/esphome/components/mcp23s17/mcp23s17.h @@ -1,47 +1,14 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/components/mcp23x17_base/mcp23x17_base.h" #include "esphome/core/esphal.h" #include "esphome/components/spi/spi.h" namespace esphome { namespace mcp23s17 { -/// Modes for MCP23S17 pins -enum MCP23S17GPIOMode : uint8_t { - MCP23S17_INPUT = INPUT, // 0x00 - MCP23S17_INPUT_PULLUP = INPUT_PULLUP, // 0x02 - MCP23S17_OUTPUT = OUTPUT // 0x01 -}; - -enum MCP23S17GPIORegisters { - // A side - MCP23S17_IODIRA = 0x00, - MCP23S17_IPOLA = 0x02, - MCP23S17_GPINTENA = 0x04, - MCP23S17_DEFVALA = 0x06, - MCP23S17_INTCONA = 0x08, - MCP23S17_IOCONA = 0x0A, - MCP23S17_GPPUA = 0x0C, - MCP23S17_INTFA = 0x0E, - MCP23S17_INTCAPA = 0x10, - MCP23S17_GPIOA = 0x12, - MCP23S17_OLATA = 0x14, - // B side - MCP23S17_IODIRB = 0x01, - MCP23S17_IPOLB = 0x03, - MCP23S17_GPINTENB = 0x05, - MCP23S17_DEFVALB = 0x07, - MCP23S17_INTCONB = 0x09, - MCP23S17_IOCONB = 0x0B, - MCP23S17_GPPUB = 0x0D, - MCP23S17_INTFB = 0x0F, - MCP23S17_INTCAPB = 0x11, - MCP23S17_GPIOB = 0x13, - MCP23S17_OLATB = 0x15, -}; - -class MCP23S17 : public Component, +class MCP23S17 : public mcp23x17_base::MCP23X17Base, public spi::SPIDevice { public: @@ -51,36 +18,11 @@ class MCP23S17 : public Component, void dump_config() override; void set_device_address(uint8_t device_addr); - bool digital_read(uint8_t pin); - void digital_write(uint8_t pin, bool value); - void pin_mode(uint8_t pin, uint8_t mode); - - float get_setup_priority() const override; - - // read a given register - bool read_reg(uint8_t reg, uint8_t *value); - // write a value to a given register - bool write_reg(uint8_t reg, uint8_t value); - // update registers with given pin value. - void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a); - protected: - uint8_t device_opcode_ = 0x40; - uint8_t olat_a_{0x00}; - uint8_t olat_b_{0x00}; -}; - -class MCP23S17GPIOPin : public GPIOPin { - public: - MCP23S17GPIOPin(MCP23S17 *parent, uint8_t pin, uint8_t mode, bool inverted = false); + bool read_reg(uint8_t reg, uint8_t *value) override; + bool write_reg(uint8_t reg, uint8_t value) override; - void setup() override; - void pin_mode(uint8_t mode) override; - bool digital_read() override; - void digital_write(bool value) override; - - protected: - MCP23S17 *parent_; + uint8_t device_opcode_ = 0x40; }; } // namespace mcp23s17 diff --git a/esphome/components/mcp23x08_base/__init__.py b/esphome/components/mcp23x08_base/__init__.py new file mode 100644 index 000000000000..ba449172028e --- /dev/null +++ b/esphome/components/mcp23x08_base/__init__.py @@ -0,0 +1,8 @@ +import esphome.codegen as cg +from esphome.components import mcp23xxx_base + +AUTO_LOAD = ["mcp23xxx_base"] +CODEOWNERS = ["@jesserockz"] + +mcp23x08_base_ns = cg.esphome_ns.namespace("mcp23x08_base") +MCP23X08Base = mcp23x08_base_ns.class_("MCP23X08Base", mcp23xxx_base.MCP23XXXBase) diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.cpp b/esphome/components/mcp23x08_base/mcp23x08_base.cpp new file mode 100644 index 000000000000..c14e0020dd83 --- /dev/null +++ b/esphome/components/mcp23x08_base/mcp23x08_base.cpp @@ -0,0 +1,89 @@ +#include "mcp23x08_base.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp23x08_base { + +static const char *TAG = "mcp23x08_base"; + +bool MCP23X08Base::digital_read(uint8_t pin) { + uint8_t bit = pin % 8; + uint8_t reg_addr = mcp23x08_base::MCP23X08_GPIO; + uint8_t value = 0; + this->read_reg(reg_addr, &value); + return value & (1 << bit); +} + +void MCP23X08Base::digital_write(uint8_t pin, bool value) { + uint8_t reg_addr = mcp23x08_base::MCP23X08_OLAT; + this->update_reg(pin, value, reg_addr); +} + +void MCP23X08Base::pin_mode(uint8_t pin, uint8_t mode) { + uint8_t iodir = mcp23x08_base::MCP23X08_IODIR; + uint8_t gppu = mcp23x08_base::MCP23X08_GPPU; + switch (mode) { + case mcp23xxx_base::MCP23XXX_INPUT: + this->update_reg(pin, true, iodir); + break; + case mcp23xxx_base::MCP23XXX_INPUT_PULLUP: + this->update_reg(pin, true, iodir); + this->update_reg(pin, true, gppu); + break; + case mcp23xxx_base::MCP23XXX_OUTPUT: + this->update_reg(pin, false, iodir); + break; + default: + break; + } +} + +void MCP23X08Base::pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) { + uint8_t gpinten = mcp23x08_base::MCP23X08_GPINTEN; + uint8_t intcon = mcp23x08_base::MCP23X08_INTCON; + uint8_t defval = mcp23x08_base::MCP23X08_DEFVAL; + + switch (interrupt_mode) { + case mcp23xxx_base::MCP23XXX_CHANGE: + this->update_reg(pin, true, gpinten); + this->update_reg(pin, false, intcon); + break; + case mcp23xxx_base::MCP23XXX_RISING: + this->update_reg(pin, true, gpinten); + this->update_reg(pin, true, intcon); + this->update_reg(pin, true, defval); + break; + case mcp23xxx_base::MCP23XXX_FALLING: + this->update_reg(pin, true, gpinten); + this->update_reg(pin, true, intcon); + this->update_reg(pin, false, defval); + break; + case mcp23xxx_base::MCP23XXX_NO_INTERRUPT: + this->update_reg(pin, false, gpinten); + break; + } +} + +void MCP23X08Base::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) { + uint8_t bit = pin % 8; + uint8_t reg_value = 0; + if (reg_addr == mcp23x08_base::MCP23X08_OLAT) { + reg_value = this->olat_; + } else { + this->read_reg(reg_addr, ®_value); + } + + if (pin_value) + reg_value |= 1 << bit; + else + reg_value &= ~(1 << bit); + + this->write_reg(reg_addr, reg_value); + + if (reg_addr == mcp23x08_base::MCP23X08_OLAT) { + this->olat_ = reg_value; + } +} + +} // namespace mcp23x08_base +} // namespace esphome diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.h b/esphome/components/mcp23x08_base/mcp23x08_base.h new file mode 100644 index 000000000000..5e2c1a047f54 --- /dev/null +++ b/esphome/components/mcp23x08_base/mcp23x08_base.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/mcp23xxx_base/mcp23xxx_base.h" +#include "esphome/core/esphal.h" + +namespace esphome { +namespace mcp23x08_base { + +enum MCP23S08GPIORegisters { + // A side + MCP23X08_IODIR = 0x00, + MCP23X08_IPOL = 0x01, + MCP23X08_GPINTEN = 0x02, + MCP23X08_DEFVAL = 0x03, + MCP23X08_INTCON = 0x04, + MCP23X08_IOCON = 0x05, + MCP23X08_GPPU = 0x06, + MCP23X08_INTF = 0x07, + MCP23X08_INTCAP = 0x08, + MCP23X08_GPIO = 0x09, + MCP23X08_OLAT = 0x0A, +}; + +class MCP23X08Base : public mcp23xxx_base::MCP23XXXBase { + public: + bool digital_read(uint8_t pin) override; + void digital_write(uint8_t pin, bool value) override; + void pin_mode(uint8_t pin, uint8_t mode) override; + void pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) override; + + protected: + void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a) override; + + uint8_t olat_{0x00}; +}; + +} // namespace mcp23x08_base +} // namespace esphome diff --git a/esphome/components/mcp23x17_base/__init__.py b/esphome/components/mcp23x17_base/__init__.py new file mode 100644 index 000000000000..97e0b3823d20 --- /dev/null +++ b/esphome/components/mcp23x17_base/__init__.py @@ -0,0 +1,8 @@ +import esphome.codegen as cg +from esphome.components import mcp23xxx_base + +AUTO_LOAD = ["mcp23xxx_base"] +CODEOWNERS = ["@jesserockz"] + +mcp23x17_base_ns = cg.esphome_ns.namespace("mcp23x17_base") +MCP23X17Base = mcp23x17_base_ns.class_("MCP23X17Base", mcp23xxx_base.MCP23XXXBase) diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.cpp b/esphome/components/mcp23x17_base/mcp23x17_base.cpp new file mode 100644 index 000000000000..18f3ba7c6d33 --- /dev/null +++ b/esphome/components/mcp23x17_base/mcp23x17_base.cpp @@ -0,0 +1,93 @@ +#include "mcp23x17_base.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp23x17_base { + +static const char *TAG = "mcp23x17_base"; + +bool MCP23X17Base::digital_read(uint8_t pin) { + uint8_t bit = pin % 8; + uint8_t reg_addr = pin < 8 ? mcp23x17_base::MCP23X17_GPIOA : mcp23x17_base::MCP23X17_GPIOB; + uint8_t value = 0; + this->read_reg(reg_addr, &value); + return value & (1 << bit); +} + +void MCP23X17Base::digital_write(uint8_t pin, bool value) { + uint8_t reg_addr = pin < 8 ? mcp23x17_base::MCP23X17_OLATA : mcp23x17_base::MCP23X17_OLATB; + this->update_reg(pin, value, reg_addr); +} + +void MCP23X17Base::pin_mode(uint8_t pin, uint8_t mode) { + uint8_t iodir = pin < 8 ? mcp23x17_base::MCP23X17_IODIRA : mcp23x17_base::MCP23X17_IODIRB; + uint8_t gppu = pin < 8 ? mcp23x17_base::MCP23X17_GPPUA : mcp23x17_base::MCP23X17_GPPUB; + switch (mode) { + case mcp23xxx_base::MCP23XXX_INPUT: + this->update_reg(pin, true, iodir); + break; + case mcp23xxx_base::MCP23XXX_INPUT_PULLUP: + this->update_reg(pin, true, iodir); + this->update_reg(pin, true, gppu); + break; + case mcp23xxx_base::MCP23XXX_OUTPUT: + this->update_reg(pin, false, iodir); + break; + default: + break; + } +} + +void MCP23X17Base::pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) { + uint8_t gpinten = pin < 8 ? mcp23x17_base::MCP23X17_GPINTENA : mcp23x17_base::MCP23X17_GPINTENB; + uint8_t intcon = pin < 8 ? mcp23x17_base::MCP23X17_INTCONA : mcp23x17_base::MCP23X17_INTCONB; + uint8_t defval = pin < 8 ? mcp23x17_base::MCP23X17_DEFVALA : mcp23x17_base::MCP23X17_DEFVALB; + + switch (interrupt_mode) { + case mcp23xxx_base::MCP23XXX_CHANGE: + this->update_reg(pin, true, gpinten); + this->update_reg(pin, false, intcon); + break; + case mcp23xxx_base::MCP23XXX_RISING: + this->update_reg(pin, true, gpinten); + this->update_reg(pin, true, intcon); + this->update_reg(pin, true, defval); + break; + case mcp23xxx_base::MCP23XXX_FALLING: + this->update_reg(pin, true, gpinten); + this->update_reg(pin, true, intcon); + this->update_reg(pin, false, defval); + break; + case mcp23xxx_base::MCP23XXX_NO_INTERRUPT: + this->update_reg(pin, false, gpinten); + break; + } +} + +void MCP23X17Base::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) { + uint8_t bit = pin % 8; + uint8_t reg_value = 0; + if (reg_addr == mcp23x17_base::MCP23X17_OLATA) { + reg_value = this->olat_a_; + } else if (reg_addr == mcp23x17_base::MCP23X17_OLATB) { + reg_value = this->olat_b_; + } else { + this->read_reg(reg_addr, ®_value); + } + + if (pin_value) + reg_value |= 1 << bit; + else + reg_value &= ~(1 << bit); + + this->write_reg(reg_addr, reg_value); + + if (reg_addr == mcp23x17_base::MCP23X17_OLATA) { + this->olat_a_ = reg_value; + } else if (reg_addr == mcp23x17_base::MCP23X17_OLATB) { + this->olat_b_ = reg_value; + } +} + +} // namespace mcp23x17_base +} // namespace esphome diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.h b/esphome/components/mcp23x17_base/mcp23x17_base.h new file mode 100644 index 000000000000..1bbcb97041f7 --- /dev/null +++ b/esphome/components/mcp23x17_base/mcp23x17_base.h @@ -0,0 +1,52 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/mcp23xxx_base/mcp23xxx_base.h" +#include "esphome/core/esphal.h" + +namespace esphome { +namespace mcp23x17_base { + +enum MCP23X17GPIORegisters { + // A side + MCP23X17_IODIRA = 0x00, + MCP23X17_IPOLA = 0x02, + MCP23X17_GPINTENA = 0x04, + MCP23X17_DEFVALA = 0x06, + MCP23X17_INTCONA = 0x08, + MCP23X17_IOCONA = 0x0A, + MCP23X17_GPPUA = 0x0C, + MCP23X17_INTFA = 0x0E, + MCP23X17_INTCAPA = 0x10, + MCP23X17_GPIOA = 0x12, + MCP23X17_OLATA = 0x14, + // B side + MCP23X17_IODIRB = 0x01, + MCP23X17_IPOLB = 0x03, + MCP23X17_GPINTENB = 0x05, + MCP23X17_DEFVALB = 0x07, + MCP23X17_INTCONB = 0x09, + MCP23X17_IOCONB = 0x0B, + MCP23X17_GPPUB = 0x0D, + MCP23X17_INTFB = 0x0F, + MCP23X17_INTCAPB = 0x11, + MCP23X17_GPIOB = 0x13, + MCP23X17_OLATB = 0x15, +}; + +class MCP23X17Base : public mcp23xxx_base::MCP23XXXBase { + public: + bool digital_read(uint8_t pin) override; + void digital_write(uint8_t pin, bool value) override; + void pin_mode(uint8_t pin, uint8_t mode) override; + void pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) override; + + protected: + void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a) override; + + uint8_t olat_a_{0x00}; + uint8_t olat_b_{0x00}; +}; + +} // namespace mcp23x17_base +} // namespace esphome diff --git a/esphome/components/mcp23xxx_base/__init__.py b/esphome/components/mcp23xxx_base/__init__.py new file mode 100644 index 000000000000..f8ab224193d4 --- /dev/null +++ b/esphome/components/mcp23xxx_base/__init__.py @@ -0,0 +1,118 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.const import ( + CONF_ID, + CONF_NUMBER, + CONF_MODE, + CONF_INVERTED, + CONF_INTERRUPT, + CONF_OPEN_DRAIN_INTERRUPT, +) +from esphome.core import coroutine + +CODEOWNERS = ["@jesserockz"] + +mcp23xxx_base_ns = cg.esphome_ns.namespace("mcp23xxx_base") +MCP23XXXBase = mcp23xxx_base_ns.class_("MCP23XXXBase", cg.Component) +MCP23XXXGPIOPin = mcp23xxx_base_ns.class_("MCP23XXXGPIOPin", cg.GPIOPin) +MCP23XXXGPIOMode = mcp23xxx_base_ns.enum("MCP23XXXGPIOMode") +MCP23XXXInterruptMode = mcp23xxx_base_ns.enum("MCP23XXXInterruptMode") + +MCP23XXX_INTERRUPT_MODES = { + "NO_INTERRUPT": MCP23XXXInterruptMode.MCP23XXX_NO_INTERRUPT, + "CHANGE": MCP23XXXInterruptMode.MCP23XXX_CHANGE, + "RISING": MCP23XXXInterruptMode.MCP23XXX_RISING, + "FALLING": MCP23XXXInterruptMode.MCP23XXX_FALLING, +} + +MCP23XXX_GPIO_MODES = { + "INPUT": MCP23XXXGPIOMode.MCP23XXX_INPUT, + "INPUT_PULLUP": MCP23XXXGPIOMode.MCP23XXX_INPUT_PULLUP, + "OUTPUT": MCP23XXXGPIOMode.MCP23XXX_OUTPUT, +} + +MCP23XXX_CONFIG_SCHEMA = cv.Schema( + { + cv.Optional(CONF_OPEN_DRAIN_INTERRUPT, default=False): cv.boolean, + } +).extend(cv.COMPONENT_SCHEMA) + + +@coroutine +def register_mcp23xxx(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + cg.add(var.set_open_drain_ints(config[CONF_OPEN_DRAIN_INTERRUPT])) + return var + + +CONF_MCP23XXX = "mcp23xxx" +MCP23XXX_OUTPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_MCP23XXX): cv.use_id(MCP23XXXBase), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum( + MCP23XXX_GPIO_MODES, upper=True + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( + MCP23XXX_INTERRUPT_MODES, upper=True + ), + } +) +MCP23XXX_INPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_MCP23XXX): cv.use_id(MCP23XXXBase), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="INPUT"): cv.enum( + MCP23XXX_GPIO_MODES, upper=True + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( + MCP23XXX_INTERRUPT_MODES, upper=True + ), + } +) + + +@pins.PIN_SCHEMA_REGISTRY.register( + CONF_MCP23XXX, (MCP23XXX_OUTPUT_PIN_SCHEMA, MCP23XXX_INPUT_PIN_SCHEMA) +) +def mcp23xxx_pin_to_code(config): + parent = yield cg.get_variable(config[CONF_MCP23XXX]) + yield MCP23XXXGPIOPin.new( + parent, + config[CONF_NUMBER], + config[CONF_MODE], + config[CONF_INVERTED], + config[CONF_INTERRUPT], + ) + + +# BEGIN Removed pin schemas below to show error in configuration +# TODO remove in 1.19.0 + +for id in ["mcp23008", "mcp23s08", "mcp23017", "mcp23s17"]: + PIN_SCHEMA = cv.Schema( + { + cv.Required(id): cv.invalid( + f"'{id}:' has been removed from the pin schema in 1.17.0, please use 'mcp23xxx:'" + ), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="INPUT"): cv.enum( + MCP23XXX_GPIO_MODES, upper=True + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( + MCP23XXX_INTERRUPT_MODES, upper=True + ), + } + ) + + @pins.PIN_SCHEMA_REGISTRY.register(id, (PIN_SCHEMA, PIN_SCHEMA)) + def pin_to_code(config): + pass + + +# END Removed pin schemas diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp new file mode 100644 index 000000000000..37c55fceaf92 --- /dev/null +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp @@ -0,0 +1,21 @@ +#include "mcp23xxx_base.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp23xxx_base { + +float MCP23XXXBase::get_setup_priority() const { return setup_priority::IO; } + +MCP23XXXGPIOPin::MCP23XXXGPIOPin(MCP23XXXBase *parent, uint8_t pin, uint8_t mode, bool inverted, + MCP23XXXInterruptMode interrupt_mode) + : GPIOPin(pin, mode, inverted), parent_(parent), interrupt_mode_(interrupt_mode) {} +void MCP23XXXGPIOPin::setup() { this->pin_mode(this->mode_); } +void MCP23XXXGPIOPin::pin_mode(uint8_t mode) { + this->parent_->pin_mode(this->pin_, mode); + this->parent_->pin_interrupt_mode(this->pin_, this->interrupt_mode_); +} +bool MCP23XXXGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } +void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } + +} // namespace mcp23xxx_base +} // namespace esphome diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.h b/esphome/components/mcp23xxx_base/mcp23xxx_base.h new file mode 100644 index 000000000000..bf013202647b --- /dev/null +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.h @@ -0,0 +1,55 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/esphal.h" + +namespace esphome { +namespace mcp23xxx_base { + +enum MCP23XXXInterruptMode : uint8_t { MCP23XXX_NO_INTERRUPT = 0, MCP23XXX_CHANGE, MCP23XXX_RISING, MCP23XXX_FALLING }; + +/// Modes for MCP23XXX pins +enum MCP23XXXGPIOMode : uint8_t { + MCP23XXX_INPUT = INPUT, // 0x00 + MCP23XXX_INPUT_PULLUP = INPUT_PULLUP, // 0x02 + MCP23XXX_OUTPUT = OUTPUT // 0x01 +}; + +class MCP23XXXBase : public Component { + public: + virtual bool digital_read(uint8_t pin); + virtual void digital_write(uint8_t pin, bool value); + virtual void pin_mode(uint8_t pin, uint8_t mode); + virtual void pin_interrupt_mode(uint8_t pin, MCP23XXXInterruptMode interrupt_mode); + + void set_open_drain_ints(const bool value) { this->open_drain_ints_ = value; } + float get_setup_priority() const override; + + protected: + // read a given register + virtual bool read_reg(uint8_t reg, uint8_t *value); + // write a value to a given register + virtual bool write_reg(uint8_t reg, uint8_t value); + // update registers with given pin value. + virtual void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a); + + bool open_drain_ints_; +}; + +class MCP23XXXGPIOPin : public GPIOPin { + public: + MCP23XXXGPIOPin(MCP23XXXBase *parent, uint8_t pin, uint8_t mode, bool inverted = false, + MCP23XXXInterruptMode interrupt_mode = MCP23XXX_NO_INTERRUPT); + + void setup() override; + void pin_mode(uint8_t mode) override; + bool digital_read() override; + void digital_write(bool value) override; + + protected: + MCP23XXXBase *parent_; + MCP23XXXInterruptMode interrupt_mode_; +}; + +} // namespace mcp23xxx_base +} // namespace esphome diff --git a/esphome/components/mcp2515/canbus.py b/esphome/components/mcp2515/canbus.py index a877507e3631..0fc679d17aa0 100644 --- a/esphome/components/mcp2515/canbus.py +++ b/esphome/components/mcp2515/canbus.py @@ -4,33 +4,35 @@ from esphome.const import CONF_ID, CONF_MODE from esphome.components.canbus import CanbusComponent -CODEOWNERS = ['@mvturnho', '@danielschramm'] -DEPENDENCIES = ['spi'] +CODEOWNERS = ["@mvturnho", "@danielschramm"] +DEPENDENCIES = ["spi"] -CONF_CLOCK = 'clock' +CONF_CLOCK = "clock" -mcp2515_ns = cg.esphome_ns.namespace('mcp2515') -mcp2515 = mcp2515_ns.class_('MCP2515', CanbusComponent, spi.SPIDevice) -CanClock = mcp2515_ns.enum('CAN_CLOCK') -McpMode = mcp2515_ns.enum('CANCTRL_REQOP_MODE') +mcp2515_ns = cg.esphome_ns.namespace("mcp2515") +mcp2515 = mcp2515_ns.class_("MCP2515", CanbusComponent, spi.SPIDevice) +CanClock = mcp2515_ns.enum("CAN_CLOCK") +McpMode = mcp2515_ns.enum("CANCTRL_REQOP_MODE") CAN_CLOCK = { - '8MHZ': CanClock.MCP_8MHZ, - '16MHZ': CanClock.MCP_16MHZ, - '20MHZ': CanClock.MCP_20MHZ, + "8MHZ": CanClock.MCP_8MHZ, + "16MHZ": CanClock.MCP_16MHZ, + "20MHZ": CanClock.MCP_20MHZ, } MCP_MODE = { - 'NORMAL': McpMode.CANCTRL_REQOP_NORMAL, - 'LOOPBACK': McpMode.CANCTRL_REQOP_LOOPBACK, - 'LISTENONLY': McpMode.CANCTRL_REQOP_LISTENONLY, + "NORMAL": McpMode.CANCTRL_REQOP_NORMAL, + "LOOPBACK": McpMode.CANCTRL_REQOP_LOOPBACK, + "LISTENONLY": McpMode.CANCTRL_REQOP_LISTENONLY, } -CONFIG_SCHEMA = canbus.CONFIG_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(mcp2515), - cv.Optional(CONF_CLOCK, default='8MHZ'): cv.enum(CAN_CLOCK, upper=True), - cv.Optional(CONF_MODE, default='NORMAL'): cv.enum(MCP_MODE, upper=True), -}).extend(spi.spi_device_schema(True)) +CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(mcp2515), + cv.Optional(CONF_CLOCK, default="8MHZ"): cv.enum(CAN_CLOCK, upper=True), + cv.Optional(CONF_MODE, default="NORMAL"): cv.enum(MCP_MODE, upper=True), + } +).extend(spi.spi_device_schema(True)) def to_code(config): diff --git a/esphome/components/mcp3008/__init__.py b/esphome/components/mcp3008/__init__.py index acacc9615948..6a0410a75b08 100644 --- a/esphome/components/mcp3008/__init__.py +++ b/esphome/components/mcp3008/__init__.py @@ -3,18 +3,20 @@ from esphome.components import spi from esphome.const import CONF_ID -DEPENDENCIES = ['spi'] -AUTO_LOAD = ['sensor'] +DEPENDENCIES = ["spi"] +AUTO_LOAD = ["sensor"] MULTI_CONF = True -CONF_MCP3008 = 'mcp3008' +CONF_MCP3008 = "mcp3008" -mcp3008_ns = cg.esphome_ns.namespace('mcp3008') -MCP3008 = mcp3008_ns.class_('MCP3008', cg.Component, spi.SPIDevice) +mcp3008_ns = cg.esphome_ns.namespace("mcp3008") +MCP3008 = mcp3008_ns.class_("MCP3008", cg.Component, spi.SPIDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(MCP3008), -}).extend(spi.spi_device_schema(cs_pin_required=True)) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(MCP3008), + } +).extend(spi.spi_device_schema(cs_pin_required=True)) def to_code(config): diff --git a/esphome/components/mcp3008/sensor.py b/esphome/components/mcp3008/sensor.py index 9248d51a4292..b14615855292 100644 --- a/esphome/components/mcp3008/sensor.py +++ b/esphome/components/mcp3008/sensor.py @@ -4,26 +4,34 @@ from esphome.const import CONF_ID, CONF_NUMBER, CONF_NAME from . import mcp3008_ns, MCP3008 -AUTO_LOAD = ['voltage_sampler'] +AUTO_LOAD = ["voltage_sampler"] -DEPENDENCIES = ['mcp3008'] +DEPENDENCIES = ["mcp3008"] -MCP3008Sensor = mcp3008_ns.class_('MCP3008Sensor', sensor.Sensor, cg.PollingComponent, - voltage_sampler.VoltageSampler) -CONF_REFERENCE_VOLTAGE = 'reference_voltage' -CONF_MCP3008_ID = 'mcp3008_id' +MCP3008Sensor = mcp3008_ns.class_( + "MCP3008Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler +) +CONF_REFERENCE_VOLTAGE = "reference_voltage" +CONF_MCP3008_ID = "mcp3008_id" -CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(MCP3008Sensor), - cv.GenerateID(CONF_MCP3008_ID): cv.use_id(MCP3008), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_REFERENCE_VOLTAGE, default='3.3V'): cv.voltage, -}).extend(cv.polling_component_schema('1s')) +CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MCP3008Sensor), + cv.GenerateID(CONF_MCP3008_ID): cv.use_id(MCP3008), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage, + } +).extend(cv.polling_component_schema("1s")) def to_code(config): parent = yield cg.get_variable(config[CONF_MCP3008_ID]) - var = cg.new_Pvariable(config[CONF_ID], parent, config[CONF_NAME], - config[CONF_NUMBER], config[CONF_REFERENCE_VOLTAGE]) + var = cg.new_Pvariable( + config[CONF_ID], + parent, + config[CONF_NAME], + config[CONF_NUMBER], + config[CONF_REFERENCE_VOLTAGE], + ) yield cg.register_component(var, config) yield sensor.register_sensor(var, config) diff --git a/esphome/components/mcp4725/__init__.py b/esphome/components/mcp4725/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/mcp4725/mcp4725.cpp b/esphome/components/mcp4725/mcp4725.cpp new file mode 100644 index 000000000000..85028d2f3947 --- /dev/null +++ b/esphome/components/mcp4725/mcp4725.cpp @@ -0,0 +1,38 @@ +#include "mcp4725.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp4725 { + +static const char *TAG = "mcp4725"; + +void MCP4725::setup() { + ESP_LOGCONFIG(TAG, "Setting up MCP4725 (0x%02X)...", this->address_); + + this->parent_->raw_begin_transmission(this->address_); + + if (!this->parent_->raw_end_transmission(this->address_)) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + + return; + } +} + +void MCP4725::dump_config() { + LOG_I2C_DEVICE(this); + + if (this->error_code_ == COMMUNICATION_FAILED) { + ESP_LOGE(TAG, "Communication with MCP4725 failed!"); + } +} + +// https://learn.sparkfun.com/tutorials/mcp4725-digital-to-analog-converter-hookup-guide?_ga=2.176055202.1402343014.1607953301-893095255.1606753886 +void MCP4725::write_state(float state) { + const uint16_t value = (uint16_t) round(state * (pow(2, MCP4725_RES) - 1)); + + this->write_byte_16(64, value << 4); +} + +} // namespace mcp4725 +} // namespace esphome diff --git a/esphome/components/mcp4725/mcp4725.h b/esphome/components/mcp4725/mcp4725.h new file mode 100644 index 000000000000..d6fa52e323e2 --- /dev/null +++ b/esphome/components/mcp4725/mcp4725.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/components/output/float_output.h" +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" + +static const uint8_t MCP4725_ADDR = 0x60; +static const uint8_t MCP4725_RES = 12; + +namespace esphome { +namespace mcp4725 { +class MCP4725 : public Component, public output::FloatOutput, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void write_state(float state) override; + + protected: + enum ErrorCode { NONE = 0, COMMUNICATION_FAILED } error_code_{NONE}; +}; + +} // namespace mcp4725 +} // namespace esphome diff --git a/esphome/components/mcp4725/output.py b/esphome/components/mcp4725/output.py new file mode 100644 index 000000000000..a010ca99700e --- /dev/null +++ b/esphome/components/mcp4725/output.py @@ -0,0 +1,26 @@ +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.components import output, i2c +from esphome.const import CONF_ID + +DEPENDENCIES = ["i2c"] + +mcp4725 = cg.esphome_ns.namespace("mcp4725") +MCP4725 = mcp4725.class_("MCP4725", output.FloatOutput, cg.Component, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(MCP4725), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x60)) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield i2c.register_i2c_device(var, config) + yield output.register_output(var, config) diff --git a/esphome/components/mcp9808/sensor.py b/esphome/components/mcp9808/sensor.py index f1ccfadc547a..4973d41ff569 100644 --- a/esphome/components/mcp9808/sensor.py +++ b/esphome/components/mcp9808/sensor.py @@ -1,18 +1,26 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, ICON_THERMOMETER, UNIT_CELSIUS +from esphome.const import CONF_ID, DEVICE_CLASS_TEMPERATURE, ICON_EMPTY, UNIT_CELSIUS -CODEOWNERS = ['@k7hpn'] -DEPENDENCIES = ['i2c'] +CODEOWNERS = ["@k7hpn"] +DEPENDENCIES = ["i2c"] -mcp9808_ns = cg.esphome_ns.namespace('mcp9808') -MCP9808Sensor = mcp9808_ns.class_('MCP9808Sensor', sensor.Sensor, cg.PollingComponent, - i2c.I2CDevice) +mcp9808_ns = cg.esphome_ns.namespace("mcp9808") +MCP9808Sensor = mcp9808_ns.class_( + "MCP9808Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.GenerateID(): cv.declare_id(MCP9808Sensor), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x18)) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE) + .extend( + { + cv.GenerateID(): cv.declare_id(MCP9808Sensor), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x18)) +) def to_code(config): diff --git a/esphome/components/mhz19/sensor.py b/esphome/components/mhz19/sensor.py index d0a7caee8401..6989814ada23 100644 --- a/esphome/components/mhz19/sensor.py +++ b/esphome/components/mhz19/sensor.py @@ -3,25 +3,46 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import sensor, uart -from esphome.const import CONF_CO2, CONF_ID, CONF_TEMPERATURE, ICON_MOLECULE_CO2, \ - UNIT_PARTS_PER_MILLION, UNIT_CELSIUS, ICON_THERMOMETER +from esphome.const import ( + CONF_CO2, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TEMPERATURE, + ICON_MOLECULE_CO2, + UNIT_PARTS_PER_MILLION, + UNIT_CELSIUS, + ICON_EMPTY, +) -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] -CONF_AUTOMATIC_BASELINE_CALIBRATION = 'automatic_baseline_calibration' +CONF_AUTOMATIC_BASELINE_CALIBRATION = "automatic_baseline_calibration" -mhz19_ns = cg.esphome_ns.namespace('mhz19') -MHZ19Component = mhz19_ns.class_('MHZ19Component', cg.PollingComponent, uart.UARTDevice) -MHZ19CalibrateZeroAction = mhz19_ns.class_('MHZ19CalibrateZeroAction', automation.Action) -MHZ19ABCEnableAction = mhz19_ns.class_('MHZ19ABCEnableAction', automation.Action) -MHZ19ABCDisableAction = mhz19_ns.class_('MHZ19ABCDisableAction', automation.Action) +mhz19_ns = cg.esphome_ns.namespace("mhz19") +MHZ19Component = mhz19_ns.class_("MHZ19Component", cg.PollingComponent, uart.UARTDevice) +MHZ19CalibrateZeroAction = mhz19_ns.class_( + "MHZ19CalibrateZeroAction", automation.Action +) +MHZ19ABCEnableAction = mhz19_ns.class_("MHZ19ABCEnableAction", automation.Action) +MHZ19ABCDisableAction = mhz19_ns.class_("MHZ19ABCDisableAction", automation.Action) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(MHZ19Component), - cv.Required(CONF_CO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 0), - cv.Optional(CONF_AUTOMATIC_BASELINE_CALIBRATION): cv.boolean, -}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MHZ19Component), + cv.Required(CONF_CO2): sensor.sensor_schema( + UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 0, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_AUTOMATIC_BASELINE_CALIBRATION): cv.boolean, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): @@ -41,17 +62,22 @@ def to_code(config): cg.add(var.set_abc_enabled(config[CONF_AUTOMATIC_BASELINE_CALIBRATION])) -CALIBRATION_ACTION_SCHEMA = maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(MHZ19Component), -}) +CALIBRATION_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(MHZ19Component), + } +) -@automation.register_action('mhz19.calibrate_zero', MHZ19CalibrateZeroAction, - CALIBRATION_ACTION_SCHEMA) -@automation.register_action('mhz19.abc_enable', MHZ19ABCEnableAction, - CALIBRATION_ACTION_SCHEMA) -@automation.register_action('mhz19.abc_disable', MHZ19ABCDisableAction, - CALIBRATION_ACTION_SCHEMA) +@automation.register_action( + "mhz19.calibrate_zero", MHZ19CalibrateZeroAction, CALIBRATION_ACTION_SCHEMA +) +@automation.register_action( + "mhz19.abc_enable", MHZ19ABCEnableAction, CALIBRATION_ACTION_SCHEMA +) +@automation.register_action( + "mhz19.abc_disable", MHZ19ABCDisableAction, CALIBRATION_ACTION_SCHEMA +) def mhz19_calibration_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/midea_ac/__init__.py b/esphome/components/midea_ac/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/midea_ac/climate.py b/esphome/components/midea_ac/climate.py new file mode 100644 index 000000000000..94aed91d4cd3 --- /dev/null +++ b/esphome/components/midea_ac/climate.py @@ -0,0 +1,69 @@ +from esphome.components import climate, sensor +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_ID, + UNIT_CELSIUS, + UNIT_PERCENT, + UNIT_WATT, + ICON_THERMOMETER, + ICON_POWER, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + ICON_WATER_PERCENT, + DEVICE_CLASS_HUMIDITY, +) +from esphome.components.midea_dongle import CONF_MIDEA_DONGLE_ID, MideaDongle + +AUTO_LOAD = ["climate", "sensor", "midea_dongle"] +CODEOWNERS = ["@dudanov"] + +CONF_BEEPER = "beeper" +CONF_SWING_HORIZONTAL = "swing_horizontal" +CONF_SWING_BOTH = "swing_both" +CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" +CONF_POWER_USAGE = "power_usage" +CONF_HUMIDITY_SETPOINT = "humidity_setpoint" +midea_ac_ns = cg.esphome_ns.namespace("midea_ac") +MideaAC = midea_ac_ns.class_("MideaAC", climate.Climate, cg.Component) + +CONFIG_SCHEMA = cv.All( + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MideaAC), + cv.GenerateID(CONF_MIDEA_DONGLE_ID): cv.use_id(MideaDongle), + cv.Optional(CONF_BEEPER, default=False): cv.boolean, + cv.Optional(CONF_SWING_HORIZONTAL, default=False): cv.boolean, + cv.Optional(CONF_SWING_BOTH, default=False): cv.boolean, + cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_THERMOMETER, 0, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_POWER_USAGE): sensor.sensor_schema( + UNIT_WATT, ICON_POWER, 0, DEVICE_CLASS_POWER + ), + cv.Optional(CONF_HUMIDITY_SETPOINT): sensor.sensor_schema( + UNIT_PERCENT, ICON_WATER_PERCENT, 0, DEVICE_CLASS_HUMIDITY + ), + } + ).extend(cv.COMPONENT_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield climate.register_climate(var, config) + paren = yield cg.get_variable(config[CONF_MIDEA_DONGLE_ID]) + cg.add(var.set_midea_dongle_parent(paren)) + cg.add(var.set_beeper_feedback(config[CONF_BEEPER])) + cg.add(var.set_swing_horizontal(config[CONF_SWING_HORIZONTAL])) + cg.add(var.set_swing_both(config[CONF_SWING_BOTH])) + if CONF_OUTDOOR_TEMPERATURE in config: + sens = yield sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) + cg.add(var.set_outdoor_temperature_sensor(sens)) + if CONF_POWER_USAGE in config: + sens = yield sensor.new_sensor(config[CONF_POWER_USAGE]) + cg.add(var.set_power_sensor(sens)) + if CONF_HUMIDITY_SETPOINT in config: + sens = yield sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT]) + cg.add(var.set_humidity_setpoint_sensor(sens)) diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp new file mode 100644 index 000000000000..8a742516969b --- /dev/null +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -0,0 +1,110 @@ +#include "esphome/core/log.h" +#include "midea_climate.h" + +namespace esphome { +namespace midea_ac { + +static const char *TAG = "midea_ac"; + +static void set_sensor(sensor::Sensor *sensor, float value) { + if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value)) + sensor->publish_state(value); +} + +template void set_property(T &property, T value, bool &flag) { + if (property != value) { + property = value; + flag = true; + } +} + +void MideaAC::on_frame(const midea_dongle::Frame &frame) { + const auto p = frame.as(); + if (p.has_power_info()) { + set_sensor(this->power_sensor_, p.get_power_usage()); + return; + } else if (!p.has_properties()) { + ESP_LOGW(TAG, "RX: frame has unknown type"); + return; + } + if (p.get_type() == midea_dongle::MideaMessageType::DEVICE_CONTROL) { + ESP_LOGD(TAG, "RX: control frame"); + this->ctrl_request_ = false; + } else { + ESP_LOGD(TAG, "RX: query frame"); + } + if (this->ctrl_request_) + return; + this->cmd_frame_.set_properties(p); // copy properties from response + bool need_publish = false; + set_property(this->mode, p.get_mode(), need_publish); + set_property(this->target_temperature, p.get_target_temp(), need_publish); + set_property(this->current_temperature, p.get_indoor_temp(), need_publish); + set_property(this->fan_mode, p.get_fan_mode(), need_publish); + set_property(this->swing_mode, p.get_swing_mode(), need_publish); + if (need_publish) + this->publish_state(); + set_sensor(this->outdoor_sensor_, p.get_outdoor_temp()); + set_sensor(this->humidity_sensor_, p.get_humidity_setpoint()); +} + +void MideaAC::on_update() { + if (this->ctrl_request_) { + ESP_LOGD(TAG, "TX: control"); + this->parent_->write_frame(this->cmd_frame_); + } else { + ESP_LOGD(TAG, "TX: query"); + if (this->power_sensor_ == nullptr || this->request_num_++ % 32) + this->parent_->write_frame(this->query_frame_); + else + this->parent_->write_frame(this->power_frame_); + } +} + +void MideaAC::control(const climate::ClimateCall &call) { + if (call.get_mode().has_value() && call.get_mode().value() != this->mode) { + this->cmd_frame_.set_mode(call.get_mode().value()); + this->ctrl_request_ = true; + } + if (call.get_target_temperature().has_value() && call.get_target_temperature().value() != this->target_temperature) { + this->cmd_frame_.set_target_temp(call.get_target_temperature().value()); + this->ctrl_request_ = true; + } + if (call.get_fan_mode().has_value() && call.get_fan_mode().value() != this->fan_mode) { + this->cmd_frame_.set_fan_mode(call.get_fan_mode().value()); + this->ctrl_request_ = true; + } + if (call.get_swing_mode().has_value() && call.get_swing_mode().value() != this->swing_mode) { + this->cmd_frame_.set_swing_mode(call.get_swing_mode().value()); + this->ctrl_request_ = true; + } + if (this->ctrl_request_) { + this->cmd_frame_.set_beeper_feedback(this->beeper_feedback_); + this->cmd_frame_.finalize(); + } +} + +climate::ClimateTraits MideaAC::traits() { + auto traits = climate::ClimateTraits(); + traits.set_visual_min_temperature(17); + traits.set_visual_max_temperature(30); + traits.set_visual_temperature_step(0.5); + traits.set_supports_auto_mode(true); + traits.set_supports_cool_mode(true); + traits.set_supports_dry_mode(true); + traits.set_supports_heat_mode(true); + traits.set_supports_fan_only_mode(true); + traits.set_supports_fan_mode_auto(true); + traits.set_supports_fan_mode_low(true); + traits.set_supports_fan_mode_medium(true); + traits.set_supports_fan_mode_high(true); + traits.set_supports_swing_mode_off(true); + traits.set_supports_swing_mode_vertical(true); + traits.set_supports_swing_mode_horizontal(this->traits_swing_horizontal_); + traits.set_supports_swing_mode_both(this->traits_swing_both_); + traits.set_supports_current_temperature(true); + return traits; +} + +} // namespace midea_ac +} // namespace esphome diff --git a/esphome/components/midea_ac/midea_climate.h b/esphome/components/midea_ac/midea_climate.h new file mode 100644 index 000000000000..f08350b252c6 --- /dev/null +++ b/esphome/components/midea_ac/midea_climate.h @@ -0,0 +1,47 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/midea_dongle/midea_dongle.h" +#include "esphome/components/climate/climate.h" +#include "midea_frame.h" + +namespace esphome { +namespace midea_ac { + +class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, public Component { + public: + float get_setup_priority() const override { return setup_priority::LATE; } + void on_frame(const midea_dongle::Frame &frame) override; + void on_update() override; + void setup() override { this->parent_->set_appliance(this); } + void set_midea_dongle_parent(midea_dongle::MideaDongle *parent) { this->parent_ = parent; } + void set_outdoor_temperature_sensor(sensor::Sensor *sensor) { this->outdoor_sensor_ = sensor; } + void set_humidity_setpoint_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; } + void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; } + void set_beeper_feedback(bool state) { this->beeper_feedback_ = state; } + void set_swing_horizontal(bool state) { this->traits_swing_horizontal_ = state; } + void set_swing_both(bool state) { this->traits_swing_both_ = state; } + + protected: + /// Override control to change settings of the climate device. + void control(const climate::ClimateCall &call) override; + /// Return the traits of this controller. + climate::ClimateTraits traits() override; + + const QueryFrame query_frame_; + const PowerQueryFrame power_frame_; + CommandFrame cmd_frame_; + midea_dongle::MideaDongle *parent_{nullptr}; + sensor::Sensor *outdoor_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + uint8_t request_num_{0}; + bool ctrl_request_{false}; + bool beeper_feedback_{false}; + bool traits_swing_horizontal_{false}; + bool traits_swing_both_{false}; +}; + +} // namespace midea_ac +} // namespace esphome diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp new file mode 100644 index 000000000000..2d9be1bdc5a7 --- /dev/null +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -0,0 +1,160 @@ +#include "midea_frame.h" + +namespace esphome { +namespace midea_ac { + +const uint8_t QueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x00, + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68}; + +const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, + 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x17, 0x6A}; + +const uint8_t CommandFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x40, 0x00, + 0x00, 0x00, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +float PropertiesFrame::get_target_temp() const { + float temp = static_cast((this->pbuf_[12] & 0x0F) + 16); + if (this->pbuf_[12] & 0x10) + temp += 0.5; + return temp; +} + +void PropertiesFrame::set_target_temp(float temp) { + uint8_t tmp = static_cast(temp * 16.0) + 4; + tmp = ((tmp & 8) << 1) | (tmp >> 4); + this->pbuf_[12] &= ~0x1F; + this->pbuf_[12] |= tmp; +} + +static float i16tof(int16_t in) { return static_cast(in - 50) / 2.0; } +float PropertiesFrame::get_indoor_temp() const { return i16tof(this->pbuf_[21]); } +float PropertiesFrame::get_outdoor_temp() const { return i16tof(this->pbuf_[22]); } +float PropertiesFrame::get_humidity_setpoint() const { return static_cast(this->pbuf_[29] & 0x7F); } + +climate::ClimateMode PropertiesFrame::get_mode() const { + if (!this->get_power_()) + return climate::CLIMATE_MODE_OFF; + switch (this->pbuf_[12] >> 5) { + case MIDEA_MODE_AUTO: + return climate::CLIMATE_MODE_AUTO; + case MIDEA_MODE_COOL: + return climate::CLIMATE_MODE_COOL; + case MIDEA_MODE_DRY: + return climate::CLIMATE_MODE_DRY; + case MIDEA_MODE_HEAT: + return climate::CLIMATE_MODE_HEAT; + case MIDEA_MODE_FAN_ONLY: + return climate::CLIMATE_MODE_FAN_ONLY; + default: + return climate::CLIMATE_MODE_OFF; + } +} + +void PropertiesFrame::set_mode(climate::ClimateMode mode) { + uint8_t m; + switch (mode) { + case climate::CLIMATE_MODE_AUTO: + m = MIDEA_MODE_AUTO; + break; + case climate::CLIMATE_MODE_COOL: + m = MIDEA_MODE_COOL; + break; + case climate::CLIMATE_MODE_DRY: + m = MIDEA_MODE_DRY; + break; + case climate::CLIMATE_MODE_HEAT: + m = MIDEA_MODE_HEAT; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + m = MIDEA_MODE_FAN_ONLY; + break; + default: + this->set_power_(false); + return; + } + this->set_power_(true); + this->pbuf_[12] &= ~0xE0; + this->pbuf_[12] |= m << 5; +} + +climate::ClimateFanMode PropertiesFrame::get_fan_mode() const { + switch (this->pbuf_[13]) { + case MIDEA_FAN_LOW: + return climate::CLIMATE_FAN_LOW; + case MIDEA_FAN_MEDIUM: + return climate::CLIMATE_FAN_MEDIUM; + case MIDEA_FAN_HIGH: + return climate::CLIMATE_FAN_HIGH; + default: + return climate::CLIMATE_FAN_AUTO; + } +} + +void PropertiesFrame::set_fan_mode(climate::ClimateFanMode mode) { + uint8_t m; + switch (mode) { + case climate::CLIMATE_FAN_LOW: + m = MIDEA_FAN_LOW; + break; + case climate::CLIMATE_FAN_MEDIUM: + m = MIDEA_FAN_MEDIUM; + break; + case climate::CLIMATE_FAN_HIGH: + m = MIDEA_FAN_HIGH; + break; + default: + m = MIDEA_FAN_AUTO; + break; + } + this->pbuf_[13] = m; +} + +climate::ClimateSwingMode PropertiesFrame::get_swing_mode() const { + switch (this->pbuf_[17] & 0x0F) { + case MIDEA_SWING_VERTICAL: + return climate::CLIMATE_SWING_VERTICAL; + case MIDEA_SWING_HORIZONTAL: + return climate::CLIMATE_SWING_HORIZONTAL; + case MIDEA_SWING_BOTH: + return climate::CLIMATE_SWING_BOTH; + default: + return climate::CLIMATE_SWING_OFF; + } +} + +void PropertiesFrame::set_swing_mode(climate::ClimateSwingMode mode) { + uint8_t m; + switch (mode) { + case climate::CLIMATE_SWING_VERTICAL: + m = MIDEA_SWING_VERTICAL; + break; + case climate::CLIMATE_SWING_HORIZONTAL: + m = MIDEA_SWING_HORIZONTAL; + break; + case climate::CLIMATE_SWING_BOTH: + m = MIDEA_SWING_BOTH; + break; + default: + m = MIDEA_SWING_OFF; + break; + } + this->pbuf_[17] = 0x30 | m; +} + +float PropertiesFrame::get_power_usage() const { + uint32_t power = 0; + const uint8_t *ptr = this->pbuf_ + 28; + for (uint32_t weight = 1;; weight *= 10, ptr--) { + power += (*ptr % 16) * weight; + weight *= 10; + power += (*ptr / 16) * weight; + if (weight == 100000) + return static_cast(power) * 0.1; + } +} + +} // namespace midea_ac +} // namespace esphome diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h new file mode 100644 index 000000000000..e07a5bf946ac --- /dev/null +++ b/esphome/components/midea_ac/midea_frame.h @@ -0,0 +1,137 @@ +#pragma once +#include "esphome/components/climate/climate.h" +#include "esphome/components/midea_dongle/midea_frame.h" + +namespace esphome { +namespace midea_ac { + +/// Enum for all modes a Midea device can be in. +enum MideaMode : uint8_t { + /// The Midea device is set to automatically change the heating/cooling cycle + MIDEA_MODE_AUTO = 1, + /// The Midea device is manually set to cool mode (not in auto mode!) + MIDEA_MODE_COOL = 2, + /// The Midea device is manually set to dry mode + MIDEA_MODE_DRY = 3, + /// The Midea device is manually set to heat mode (not in auto mode!) + MIDEA_MODE_HEAT = 4, + /// The Midea device is manually set to fan only mode + MIDEA_MODE_FAN_ONLY = 5, +}; + +/// Enum for all modes a Midea fan can be in +enum MideaFanMode : uint8_t { + /// The fan mode is set to Auto + MIDEA_FAN_AUTO = 102, + /// The fan mode is set to Low + MIDEA_FAN_LOW = 40, + /// The fan mode is set to Medium + MIDEA_FAN_MEDIUM = 60, + /// The fan mode is set to High + MIDEA_FAN_HIGH = 80, +}; + +/// Enum for all modes a Midea swing can be in +enum MideaSwingMode : uint8_t { + /// The sing mode is set to Off + MIDEA_SWING_OFF = 0b0000, + /// The fan mode is set to Both + MIDEA_SWING_BOTH = 0b1111, + /// The fan mode is set to Vertical + MIDEA_SWING_VERTICAL = 0b1100, + /// The fan mode is set to Horizontal + MIDEA_SWING_HORIZONTAL = 0b0011, +}; + +class PropertiesFrame : public midea_dongle::BaseFrame { + public: + PropertiesFrame() = delete; + PropertiesFrame(uint8_t *data) : BaseFrame(data) {} + PropertiesFrame(const Frame &frame) : BaseFrame(frame) {} + + bool has_properties() const { + return this->has_response_type(0xC0) && (this->has_type(0x03) || this->has_type(0x02)); + } + + bool has_power_info() const { return this->has_response_type(0xC1); } + + /* TARGET TEMPERATURE */ + + float get_target_temp() const; + void set_target_temp(float temp); + + /* MODE */ + climate::ClimateMode get_mode() const; + void set_mode(climate::ClimateMode mode); + + /* FAN SPEED */ + climate::ClimateFanMode get_fan_mode() const; + void set_fan_mode(climate::ClimateFanMode mode); + + /* SWING MODE */ + climate::ClimateSwingMode get_swing_mode() const; + void set_swing_mode(climate::ClimateSwingMode mode); + + /* INDOOR TEMPERATURE */ + float get_indoor_temp() const; + + /* OUTDOOR TEMPERATURE */ + float get_outdoor_temp() const; + + /* HUMIDITY SETPOINT */ + float get_humidity_setpoint() const; + + /* ECO MODE */ + bool get_eco_mode() const { return this->pbuf_[19]; } + void set_eco_mode(bool state) { this->set_bytemask_(19, 0xFF, state); } + + /* SLEEP MODE */ + bool get_sleep_mode() const { return this->pbuf_[20] & 0x01; } + void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); } + + /* TURBO MODE */ + bool get_turbo_mode() const { return this->pbuf_[20] & 0x02; } + void set_turbo_mode(bool state) { this->set_bytemask_(20, 0x02, state); } + + /* POWER USAGE */ + float get_power_usage() const; + + /// Set properties from another frame + void set_properties(const PropertiesFrame &p) { memcpy(this->pbuf_ + 11, p.data() + 11, 10); } + + protected: + /* POWER */ + bool get_power_() const { return this->pbuf_[11] & 0x01; } + void set_power_(bool state) { this->set_bytemask_(11, 0x01, state); } +}; + +// Query state frame (read-only) +class QueryFrame : public midea_dongle::StaticFrame { + public: + QueryFrame() : StaticFrame(FPSTR(this->INIT)) {} + + private: + static const uint8_t PROGMEM INIT[]; +}; + +// Power query state frame (read-only) +class PowerQueryFrame : public midea_dongle::StaticFrame { + public: + PowerQueryFrame() : StaticFrame(FPSTR(this->INIT)) {} + + private: + static const uint8_t PROGMEM INIT[]; +}; + +// Command frame +class CommandFrame : public midea_dongle::StaticFrame { + public: + CommandFrame() : StaticFrame(FPSTR(this->INIT)) {} + void set_beeper_feedback(bool state) { this->set_bytemask_(11, 0x40, state); } + + private: + static const uint8_t PROGMEM INIT[]; +}; + +} // namespace midea_ac +} // namespace esphome diff --git a/esphome/components/midea_dongle/__init__.py b/esphome/components/midea_dongle/__init__.py new file mode 100644 index 000000000000..3efeb2661d5f --- /dev/null +++ b/esphome/components/midea_dongle/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID + +DEPENDENCIES = ["wifi", "uart"] +CODEOWNERS = ["@dudanov"] + +midea_dongle_ns = cg.esphome_ns.namespace("midea_dongle") +MideaDongle = midea_dongle_ns.class_("MideaDongle", cg.Component, uart.UARTDevice) + +CONF_MIDEA_DONGLE_ID = "midea_dongle_id" +CONF_STRENGTH_ICON = "strength_icon" +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MideaDongle), + cv.Optional(CONF_STRENGTH_ICON, default=False): cv.boolean, + } + ) + .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(var.use_strength_icon(config[CONF_STRENGTH_ICON])) diff --git a/esphome/components/midea_dongle/midea_dongle.cpp b/esphome/components/midea_dongle/midea_dongle.cpp new file mode 100644 index 000000000000..8ddaba1cb6b6 --- /dev/null +++ b/esphome/components/midea_dongle/midea_dongle.cpp @@ -0,0 +1,98 @@ +#include "midea_dongle.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace midea_dongle { + +static const char *TAG = "midea_dongle"; + +void MideaDongle::loop() { + while (this->available()) { + const uint8_t rx = this->read(); + if (this->idx_ <= OFFSET_LENGTH) { + if (this->idx_ == OFFSET_LENGTH) { + if (rx <= OFFSET_BODY || rx >= sizeof(this->buf_)) { + this->reset_(); + continue; + } + this->cnt_ = rx; + } else if (rx != SYNC_BYTE) { + continue; + } + } + this->buf_[this->idx_++] = rx; + if (--this->cnt_) + continue; + this->reset_(); + const BaseFrame frame(this->buf_); + ESP_LOGD(TAG, "RX: %s", frame.to_string().c_str()); + if (!frame.is_valid()) { + ESP_LOGW(TAG, "RX: frame check failed!"); + continue; + } + if (frame.get_type() == QUERY_NETWORK) { + this->notify_.set_type(QUERY_NETWORK); + this->need_notify_ = true; + continue; + } + if (this->appliance_ != nullptr) + this->appliance_->on_frame(frame); + } +} + +void MideaDongle::update() { + const bool is_conn = WiFi.isConnected(); + uint8_t wifi_strength = 0; + if (!this->rssi_timer_) { + if (is_conn) + wifi_strength = 4; + } else if (is_conn) { + if (--this->rssi_timer_) { + wifi_strength = this->notify_.get_signal_strength(); + } else { + this->rssi_timer_ = 60; + const long dbm = WiFi.RSSI(); + if (dbm > -63) + wifi_strength = 4; + else if (dbm > -75) + wifi_strength = 3; + else if (dbm > -88) + wifi_strength = 2; + else if (dbm > -100) + wifi_strength = 1; + } + } else { + this->rssi_timer_ = 1; + } + if (this->notify_.is_connected() != is_conn) { + this->notify_.set_connected(is_conn); + this->need_notify_ = true; + } + if (this->notify_.get_signal_strength() != wifi_strength) { + this->notify_.set_signal_strength(wifi_strength); + this->need_notify_ = true; + } + if (!--this->notify_timer_) { + this->notify_.set_type(NETWORK_NOTIFY); + this->need_notify_ = true; + } + if (this->need_notify_) { + ESP_LOGD(TAG, "TX: notify WiFi STA %s, signal strength %d", is_conn ? "connected" : "not connected", wifi_strength); + this->need_notify_ = false; + this->notify_timer_ = 600; + this->notify_.finalize(); + this->write_frame(this->notify_); + return; + } + if (this->appliance_ != nullptr) + this->appliance_->on_update(); +} + +void MideaDongle::write_frame(const Frame &frame) { + this->write_array(frame.data(), frame.size()); + ESP_LOGD(TAG, "TX: %s", frame.to_string().c_str()); +} + +} // namespace midea_dongle +} // namespace esphome diff --git a/esphome/components/midea_dongle/midea_dongle.h b/esphome/components/midea_dongle/midea_dongle.h new file mode 100644 index 000000000000..a7dfb9cf256a --- /dev/null +++ b/esphome/components/midea_dongle/midea_dongle.h @@ -0,0 +1,56 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/wifi/wifi_component.h" +#include "esphome/components/uart/uart.h" +#include "midea_frame.h" + +namespace esphome { +namespace midea_dongle { + +enum MideaApplianceType : uint8_t { DEHUMIDIFIER = 0xA1, AIR_CONDITIONER = 0xAC, BROADCAST = 0xFF }; +enum MideaMessageType : uint8_t { + DEVICE_CONTROL = 0x02, + DEVICE_QUERY = 0x03, + NETWORK_NOTIFY = 0x0D, + QUERY_NETWORK = 0x63, +}; + +struct MideaAppliance { + /// Calling on update event + virtual void on_update() = 0; + /// Calling on frame receive event + virtual void on_frame(const Frame &frame) = 0; +}; + +class MideaDongle : public PollingComponent, public uart::UARTDevice { + public: + MideaDongle() : PollingComponent(1000) {} + float get_setup_priority() const override { return setup_priority::LATE; } + void update() override; + void loop() override; + void set_appliance(MideaAppliance *app) { this->appliance_ = app; } + void use_strength_icon(bool state) { this->rssi_timer_ = state; } + void write_frame(const Frame &frame); + + protected: + MideaAppliance *appliance_{nullptr}; + NotifyFrame notify_; + unsigned notify_timer_{1}; + // Buffer + uint8_t buf_[36]; + // Index + uint8_t idx_{0}; + // Reverse receive counter + uint8_t cnt_{2}; + uint8_t rssi_timer_{0}; + bool need_notify_{false}; + + // Reset receiver state + void reset_() { + this->idx_ = 0; + this->cnt_ = 2; + } +}; + +} // namespace midea_dongle +} // namespace esphome diff --git a/esphome/components/midea_dongle/midea_frame.cpp b/esphome/components/midea_dongle/midea_frame.cpp new file mode 100644 index 000000000000..acb3feee5fc5 --- /dev/null +++ b/esphome/components/midea_dongle/midea_frame.cpp @@ -0,0 +1,95 @@ +#include "midea_frame.h" + +namespace esphome { +namespace midea_dongle { + +const uint8_t BaseFrame::CRC_TABLE[] = { + 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, 0x9D, 0xC3, 0x21, + 0x7F, 0xFC, 0xA2, 0x40, 0x1E, 0x5F, 0x01, 0xE3, 0xBD, 0x3E, 0x60, 0x82, 0xDC, 0x23, 0x7D, 0x9F, 0xC1, 0x42, 0x1C, + 0xFE, 0xA0, 0xE1, 0xBF, 0x5D, 0x03, 0x80, 0xDE, 0x3C, 0x62, 0xBE, 0xE0, 0x02, 0x5C, 0xDF, 0x81, 0x63, 0x3D, 0x7C, + 0x22, 0xC0, 0x9E, 0x1D, 0x43, 0xA1, 0xFF, 0x46, 0x18, 0xFA, 0xA4, 0x27, 0x79, 0x9B, 0xC5, 0x84, 0xDA, 0x38, 0x66, + 0xE5, 0xBB, 0x59, 0x07, 0xDB, 0x85, 0x67, 0x39, 0xBA, 0xE4, 0x06, 0x58, 0x19, 0x47, 0xA5, 0xFB, 0x78, 0x26, 0xC4, + 0x9A, 0x65, 0x3B, 0xD9, 0x87, 0x04, 0x5A, 0xB8, 0xE6, 0xA7, 0xF9, 0x1B, 0x45, 0xC6, 0x98, 0x7A, 0x24, 0xF8, 0xA6, + 0x44, 0x1A, 0x99, 0xC7, 0x25, 0x7B, 0x3A, 0x64, 0x86, 0xD8, 0x5B, 0x05, 0xE7, 0xB9, 0x8C, 0xD2, 0x30, 0x6E, 0xED, + 0xB3, 0x51, 0x0F, 0x4E, 0x10, 0xF2, 0xAC, 0x2F, 0x71, 0x93, 0xCD, 0x11, 0x4F, 0xAD, 0xF3, 0x70, 0x2E, 0xCC, 0x92, + 0xD3, 0x8D, 0x6F, 0x31, 0xB2, 0xEC, 0x0E, 0x50, 0xAF, 0xF1, 0x13, 0x4D, 0xCE, 0x90, 0x72, 0x2C, 0x6D, 0x33, 0xD1, + 0x8F, 0x0C, 0x52, 0xB0, 0xEE, 0x32, 0x6C, 0x8E, 0xD0, 0x53, 0x0D, 0xEF, 0xB1, 0xF0, 0xAE, 0x4C, 0x12, 0x91, 0xCF, + 0x2D, 0x73, 0xCA, 0x94, 0x76, 0x28, 0xAB, 0xF5, 0x17, 0x49, 0x08, 0x56, 0xB4, 0xEA, 0x69, 0x37, 0xD5, 0x8B, 0x57, + 0x09, 0xEB, 0xB5, 0x36, 0x68, 0x8A, 0xD4, 0x95, 0xCB, 0x29, 0x77, 0xF4, 0xAA, 0x48, 0x16, 0xE9, 0xB7, 0x55, 0x0B, + 0x88, 0xD6, 0x34, 0x6A, 0x2B, 0x75, 0x97, 0xC9, 0x4A, 0x14, 0xF6, 0xA8, 0x74, 0x2A, 0xC8, 0x96, 0x15, 0x4B, 0xA9, + 0xF7, 0xB6, 0xE8, 0x0A, 0x54, 0xD7, 0x89, 0x6B, 0x35}; + +const uint8_t NotifyFrame::INIT[] = {0xAA, 0x1F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0D, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +bool BaseFrame::is_valid() const { return /*this->has_valid_crc_() &&*/ this->has_valid_cs_(); } + +void BaseFrame::finalize() { + this->update_crc_(); + this->update_cs_(); +} + +void BaseFrame::update_crc_() { + uint8_t crc = 0; + uint8_t *ptr = this->pbuf_ + OFFSET_BODY; + uint8_t len = this->length_() - OFFSET_BODY; + while (--len) + crc = pgm_read_byte(BaseFrame::CRC_TABLE + (crc ^ *ptr++)); + *ptr = crc; +} + +void BaseFrame::update_cs_() { + uint8_t cs = 0; + uint8_t *ptr = this->pbuf_ + OFFSET_LENGTH; + uint8_t len = this->length_(); + while (--len) + cs -= *ptr++; + *ptr = cs; +} + +bool BaseFrame::has_valid_crc_() const { + uint8_t crc = 0; + uint8_t len = this->length_() - OFFSET_BODY; + const uint8_t *ptr = this->pbuf_ + OFFSET_BODY; + for (; len; ptr++, len--) + crc = pgm_read_byte(BaseFrame::CRC_TABLE + (crc ^ *ptr)); + return !crc; +} + +bool BaseFrame::has_valid_cs_() const { + uint8_t cs = 0; + uint8_t len = this->length_(); + const uint8_t *ptr = this->pbuf_ + OFFSET_LENGTH; + for (; len; ptr++, len--) + cs -= *ptr; + return !cs; +} + +void BaseFrame::set_bytemask_(uint8_t idx, uint8_t mask, bool state) { + uint8_t *dst = this->pbuf_ + idx; + if (state) + *dst |= mask; + else + *dst &= ~mask; +} + +static char u4hex(uint8_t num) { return num + ((num < 10) ? '0' : ('A' - 10)); } + +String Frame::to_string() const { + String ret; + char buf[4]; + buf[2] = ' '; + buf[3] = '\0'; + ret.reserve(3 * 36); + const uint8_t *it = this->data(); + for (size_t i = 0; i < this->size(); i++, it++) { + buf[0] = u4hex(*it >> 4); + buf[1] = u4hex(*it & 15); + ret.concat(buf); + } + return ret; +} + +} // namespace midea_dongle +} // namespace esphome diff --git a/esphome/components/midea_dongle/midea_frame.h b/esphome/components/midea_dongle/midea_frame.h new file mode 100644 index 000000000000..ce89cc636ec0 --- /dev/null +++ b/esphome/components/midea_dongle/midea_frame.h @@ -0,0 +1,104 @@ +#pragma once +#include "esphome/core/component.h" + +namespace esphome { +namespace midea_dongle { + +static const uint8_t OFFSET_START = 0; +static const uint8_t OFFSET_LENGTH = 1; +static const uint8_t OFFSET_APPTYPE = 2; +static const uint8_t OFFSET_BODY = 10; +static const uint8_t SYNC_BYTE = 0xAA; + +class Frame { + public: + Frame() = delete; + Frame(uint8_t *data) : pbuf_(data) {} + Frame(const Frame &frame) : pbuf_(frame.data()) {} + + // Frame buffer + uint8_t *data() const { return this->pbuf_; } + // Frame size + uint8_t size() const { return this->length_() + OFFSET_LENGTH; } + uint8_t app_type() const { return this->pbuf_[OFFSET_APPTYPE]; } + + template typename std::enable_if::value, T>::type as() const { + return T(*this); + } + String to_string() const; + + protected: + uint8_t *pbuf_; + uint8_t length_() const { return this->pbuf_[OFFSET_LENGTH]; } +}; + +class BaseFrame : public Frame { + public: + BaseFrame() = delete; + BaseFrame(uint8_t *data) : Frame(data) {} + BaseFrame(const Frame &frame) : Frame(frame) {} + + // Check for valid + bool is_valid() const; + // Prepare for sending to device + void finalize(); + uint8_t get_type() const { return this->pbuf_[9]; } + void set_type(uint8_t value) { this->pbuf_[9] = value; } + bool has_response_type(uint8_t type) const { return this->resp_type_() == type; } + bool has_type(uint8_t type) const { return this->get_type() == type; } + + protected: + static const uint8_t PROGMEM CRC_TABLE[256]; + void set_bytemask_(uint8_t idx, uint8_t mask, bool state); + uint8_t resp_type_() const { return this->pbuf_[OFFSET_BODY]; } + bool has_valid_crc_() const; + bool has_valid_cs_() const; + void update_crc_(); + void update_cs_(); +}; + +template class StaticFrame : public T { + public: + // Default constructor + StaticFrame() : T(this->buf_) {} + // Copy constructor + StaticFrame(const Frame &src) : T(this->buf_) { + if (src.length_() < sizeof(this->buf_)) { + memcpy(this->buf_, src.data(), src.length_() + OFFSET_LENGTH); + } + } + // Constructor for RAM data + StaticFrame(const uint8_t *src) : T(this->buf_) { + const uint8_t len = src[OFFSET_LENGTH]; + if (len < sizeof(this->buf_)) { + memcpy(this->buf_, src, len + OFFSET_LENGTH); + } + } + // Constructor for PROGMEM data + StaticFrame(const __FlashStringHelper *pgm) : T(this->buf_) { + const uint8_t *src = reinterpret_cast(pgm); + const uint8_t len = pgm_read_byte(src + OFFSET_LENGTH); + if (len < sizeof(this->buf_)) { + memcpy_P(this->buf_, src, len + OFFSET_LENGTH); + } + } + + protected: + uint8_t buf_[buf_size]; +}; + +// Device network notification frame +class NotifyFrame : public midea_dongle::StaticFrame { + public: + NotifyFrame() : StaticFrame(FPSTR(NotifyFrame::INIT)) {} + void set_signal_strength(uint8_t value) { this->pbuf_[12] = value; } + uint8_t get_signal_strength() const { return this->pbuf_[12]; } + void set_connected(bool state) { this->pbuf_[18] = state ? 0 : 1; } + bool is_connected() const { return !this->pbuf_[18]; } + + private: + static const uint8_t PROGMEM INIT[]; +}; + +} // namespace midea_dongle +} // namespace esphome diff --git a/esphome/components/mitsubishi/climate.py b/esphome/components/mitsubishi/climate.py index 933e53baf03b..c08baf7e5472 100644 --- a/esphome/components/mitsubishi/climate.py +++ b/esphome/components/mitsubishi/climate.py @@ -3,14 +3,16 @@ from esphome.components import climate_ir from esphome.const import CONF_ID -AUTO_LOAD = ['climate_ir'] +AUTO_LOAD = ["climate_ir"] -mitsubishi_ns = cg.esphome_ns.namespace('mitsubishi') -MitsubishiClimate = mitsubishi_ns.class_('MitsubishiClimate', climate_ir.ClimateIR) +mitsubishi_ns = cg.esphome_ns.namespace("mitsubishi") +MitsubishiClimate = mitsubishi_ns.class_("MitsubishiClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(MitsubishiClimate), -}) +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MitsubishiClimate), + } +) def to_code(config): diff --git a/esphome/components/modbus/__init__.py b/esphome/components/modbus/__init__.py index cada8359057d..e71f196fcad2 100644 --- a/esphome/components/modbus/__init__.py +++ b/esphome/components/modbus/__init__.py @@ -4,17 +4,23 @@ from esphome.const import CONF_ID, CONF_ADDRESS from esphome.core import coroutine -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] -modbus_ns = cg.esphome_ns.namespace('modbus') -Modbus = modbus_ns.class_('Modbus', cg.Component, uart.UARTDevice) -ModbusDevice = modbus_ns.class_('ModbusDevice') +modbus_ns = cg.esphome_ns.namespace("modbus") +Modbus = modbus_ns.class_("Modbus", cg.Component, uart.UARTDevice) +ModbusDevice = modbus_ns.class_("ModbusDevice") MULTI_CONF = True -CONF_MODBUS_ID = 'modbus_id' -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(Modbus), -}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA) +CONF_MODBUS_ID = "modbus_id" +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Modbus), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): diff --git a/esphome/components/monochromatic/light.py b/esphome/components/monochromatic/light.py index 79faacff6c6c..32d9981a57ce 100644 --- a/esphome/components/monochromatic/light.py +++ b/esphome/components/monochromatic/light.py @@ -3,13 +3,17 @@ from esphome.components import light, output from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT -monochromatic_ns = cg.esphome_ns.namespace('monochromatic') -MonochromaticLightOutput = monochromatic_ns.class_('MonochromaticLightOutput', light.LightOutput) +monochromatic_ns = cg.esphome_ns.namespace("monochromatic") +MonochromaticLightOutput = monochromatic_ns.class_( + "MonochromaticLightOutput", light.LightOutput +) -CONFIG_SCHEMA = light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(MonochromaticLightOutput), - cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput), -}) +CONFIG_SCHEMA = light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(MonochromaticLightOutput), + cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput), + } +) def to_code(config): diff --git a/esphome/components/mpr121/__init__.py b/esphome/components/mpr121/__init__.py index b1ef9eaef5f9..eb3043c2b196 100644 --- a/esphome/components/mpr121/__init__.py +++ b/esphome/components/mpr121/__init__.py @@ -8,21 +8,31 @@ CONF_TOUCH_DEBOUNCE = "touch_debounce" CONF_RELEASE_DEBOUNCE = "release_debounce" -DEPENDENCIES = ['i2c'] -AUTO_LOAD = ['binary_sensor'] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["binary_sensor"] -mpr121_ns = cg.esphome_ns.namespace('mpr121') -CONF_MPR121_ID = 'mpr121_id' -MPR121Component = mpr121_ns.class_('MPR121Component', cg.Component, i2c.I2CDevice) +mpr121_ns = cg.esphome_ns.namespace("mpr121") +CONF_MPR121_ID = "mpr121_id" +MPR121Component = mpr121_ns.class_("MPR121Component", cg.Component, i2c.I2CDevice) MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(MPR121Component), - cv.Optional(CONF_RELEASE_DEBOUNCE, default=0): cv.int_range(min=0, max=7), - cv.Optional(CONF_TOUCH_DEBOUNCE, default=0): cv.int_range(min=0, max=7), - cv.Optional(CONF_TOUCH_THRESHOLD, default=0x0b): cv.int_range(min=0x05, max=0x30), - cv.Optional(CONF_RELEASE_THRESHOLD, default=0x06): cv.int_range(min=0x05, max=0x30), -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x5A)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MPR121Component), + cv.Optional(CONF_RELEASE_DEBOUNCE, default=0): cv.int_range(min=0, max=7), + cv.Optional(CONF_TOUCH_DEBOUNCE, default=0): cv.int_range(min=0, max=7), + cv.Optional(CONF_TOUCH_THRESHOLD, default=0x0B): cv.int_range( + min=0x05, max=0x30 + ), + cv.Optional(CONF_RELEASE_THRESHOLD, default=0x06): cv.int_range( + min=0x05, max=0x30 + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x5A)) +) def to_code(config): diff --git a/esphome/components/mpr121/binary_sensor.py b/esphome/components/mpr121/binary_sensor.py index dddfeb40e11e..68e56075f534 100644 --- a/esphome/components/mpr121/binary_sensor.py +++ b/esphome/components/mpr121/binary_sensor.py @@ -2,19 +2,26 @@ import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import CONF_CHANNEL, CONF_ID -from . import mpr121_ns, MPR121Component, CONF_MPR121_ID, CONF_TOUCH_THRESHOLD, \ - CONF_RELEASE_THRESHOLD +from . import ( + mpr121_ns, + MPR121Component, + CONF_MPR121_ID, + CONF_TOUCH_THRESHOLD, + CONF_RELEASE_THRESHOLD, +) -DEPENDENCIES = ['mpr121'] -MPR121Channel = mpr121_ns.class_('MPR121Channel', binary_sensor.BinarySensor) +DEPENDENCIES = ["mpr121"] +MPR121Channel = mpr121_ns.class_("MPR121Channel", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(MPR121Channel), - cv.GenerateID(CONF_MPR121_ID): cv.use_id(MPR121Component), - cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=11), - cv.Optional(CONF_TOUCH_THRESHOLD): cv.int_range(min=0x05, max=0x30), - cv.Optional(CONF_RELEASE_THRESHOLD): cv.int_range(min=0x05, max=0x30), -}) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MPR121Channel), + cv.GenerateID(CONF_MPR121_ID): cv.use_id(MPR121Component), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=11), + cv.Optional(CONF_TOUCH_THRESHOLD): cv.int_range(min=0x05, max=0x30), + cv.Optional(CONF_RELEASE_THRESHOLD): cv.int_range(min=0x05, max=0x30), + } +) def to_code(config): diff --git a/esphome/components/mpu6050/sensor.py b/esphome/components/mpu6050/sensor.py index 73c78e7f168d..11d491006e38 100644 --- a/esphome/components/mpu6050/sensor.py +++ b/esphome/components/mpu6050/sensor.py @@ -1,36 +1,59 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, CONF_TEMPERATURE, \ - ICON_BRIEFCASE_DOWNLOAD, UNIT_METER_PER_SECOND_SQUARED, \ - ICON_SCREEN_ROTATION, UNIT_DEGREE_PER_SECOND, ICON_THERMOMETER, UNIT_CELSIUS - -DEPENDENCIES = ['i2c'] - -CONF_ACCEL_X = 'accel_x' -CONF_ACCEL_Y = 'accel_y' -CONF_ACCEL_Z = 'accel_z' -CONF_GYRO_X = 'gyro_x' -CONF_GYRO_Y = 'gyro_y' -CONF_GYRO_Z = 'gyro_z' - -mpu6050_ns = cg.esphome_ns.namespace('mpu6050') -MPU6050Component = mpu6050_ns.class_('MPU6050Component', cg.PollingComponent, i2c.I2CDevice) - -accel_schema = sensor.sensor_schema(UNIT_METER_PER_SECOND_SQUARED, ICON_BRIEFCASE_DOWNLOAD, 2) -gyro_schema = sensor.sensor_schema(UNIT_DEGREE_PER_SECOND, ICON_SCREEN_ROTATION, 2) -temperature_schema = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(MPU6050Component), - cv.Optional(CONF_ACCEL_X): accel_schema, - cv.Optional(CONF_ACCEL_Y): accel_schema, - cv.Optional(CONF_ACCEL_Z): accel_schema, - cv.Optional(CONF_GYRO_X): gyro_schema, - cv.Optional(CONF_GYRO_Y): gyro_schema, - cv.Optional(CONF_GYRO_Z): gyro_schema, - cv.Optional(CONF_TEMPERATURE): temperature_schema, -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x68)) +from esphome.const import ( + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TEMPERATURE, + ICON_BRIEFCASE_DOWNLOAD, + ICON_EMPTY, + UNIT_METER_PER_SECOND_SQUARED, + ICON_SCREEN_ROTATION, + UNIT_DEGREE_PER_SECOND, + UNIT_CELSIUS, +) + +DEPENDENCIES = ["i2c"] + +CONF_ACCEL_X = "accel_x" +CONF_ACCEL_Y = "accel_y" +CONF_ACCEL_Z = "accel_z" +CONF_GYRO_X = "gyro_x" +CONF_GYRO_Y = "gyro_y" +CONF_GYRO_Z = "gyro_z" + +mpu6050_ns = cg.esphome_ns.namespace("mpu6050") +MPU6050Component = mpu6050_ns.class_( + "MPU6050Component", cg.PollingComponent, i2c.I2CDevice +) + +accel_schema = sensor.sensor_schema( + UNIT_METER_PER_SECOND_SQUARED, ICON_BRIEFCASE_DOWNLOAD, 2, DEVICE_CLASS_EMPTY +) +gyro_schema = sensor.sensor_schema( + UNIT_DEGREE_PER_SECOND, ICON_SCREEN_ROTATION, 2, DEVICE_CLASS_EMPTY +) +temperature_schema = sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MPU6050Component), + cv.Optional(CONF_ACCEL_X): accel_schema, + cv.Optional(CONF_ACCEL_Y): accel_schema, + cv.Optional(CONF_ACCEL_Z): accel_schema, + cv.Optional(CONF_GYRO_X): gyro_schema, + cv.Optional(CONF_GYRO_Y): gyro_schema, + cv.Optional(CONF_GYRO_Z): gyro_schema, + cv.Optional(CONF_TEMPERATURE): temperature_schema, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x68)) +) def to_code(config): @@ -38,15 +61,15 @@ def to_code(config): yield cg.register_component(var, config) yield i2c.register_i2c_device(var, config) - for d in ['x', 'y', 'z']: - accel_key = f'accel_{d}' + for d in ["x", "y", "z"]: + accel_key = f"accel_{d}" if accel_key in config: sens = yield sensor.new_sensor(config[accel_key]) - cg.add(getattr(var, f'set_accel_{d}_sensor')(sens)) - accel_key = f'gyro_{d}' + cg.add(getattr(var, f"set_accel_{d}_sensor")(sens)) + accel_key = f"gyro_{d}" if accel_key in config: sens = yield sensor.new_sensor(config[accel_key]) - cg.add(getattr(var, f'set_gyro_{d}_sensor')(sens)) + cg.add(getattr(var, f"set_gyro_{d}_sensor")(sens)) if CONF_TEMPERATURE in config: sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index db99334d0bdd..e90f90cd6a0a 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -5,17 +5,42 @@ from esphome import automation from esphome.automation import Condition from esphome.components import logger -from esphome.const import CONF_AVAILABILITY, CONF_BIRTH_MESSAGE, CONF_BROKER, CONF_CLIENT_ID, \ - CONF_COMMAND_TOPIC, CONF_DISCOVERY, CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_RETAIN, \ - CONF_ID, CONF_KEEPALIVE, CONF_LEVEL, CONF_LOG_TOPIC, CONF_ON_JSON_MESSAGE, CONF_ON_MESSAGE, \ - CONF_PASSWORD, CONF_PAYLOAD, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_PORT, \ - CONF_QOS, CONF_REBOOT_TIMEOUT, CONF_RETAIN, CONF_SHUTDOWN_MESSAGE, CONF_SSL_FINGERPRINTS, \ - CONF_STATE_TOPIC, CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_TRIGGER_ID, CONF_USERNAME, \ - CONF_WILL_MESSAGE +from esphome.const import ( + CONF_AVAILABILITY, + CONF_BIRTH_MESSAGE, + CONF_BROKER, + CONF_CLIENT_ID, + CONF_COMMAND_TOPIC, + CONF_DISCOVERY, + CONF_DISCOVERY_PREFIX, + CONF_DISCOVERY_RETAIN, + CONF_ID, + CONF_KEEPALIVE, + CONF_LEVEL, + CONF_LOG_TOPIC, + CONF_ON_JSON_MESSAGE, + CONF_ON_MESSAGE, + CONF_PASSWORD, + CONF_PAYLOAD, + CONF_PAYLOAD_AVAILABLE, + CONF_PAYLOAD_NOT_AVAILABLE, + CONF_PORT, + CONF_QOS, + CONF_REBOOT_TIMEOUT, + CONF_RETAIN, + CONF_SHUTDOWN_MESSAGE, + CONF_SSL_FINGERPRINTS, + CONF_STATE_TOPIC, + CONF_TOPIC, + CONF_TOPIC_PREFIX, + CONF_TRIGGER_ID, + CONF_USERNAME, + CONF_WILL_MESSAGE, +) from esphome.core import coroutine_with_priority, coroutine, CORE -DEPENDENCIES = ['network'] -AUTO_LOAD = ['json', 'async_tcp'] +DEPENDENCIES = ["network"] +AUTO_LOAD = ["json", "async_tcp"] def validate_message_just_topic(value): @@ -23,39 +48,49 @@ def validate_message_just_topic(value): return MQTT_MESSAGE_BASE({CONF_TOPIC: value}) -MQTT_MESSAGE_BASE = cv.Schema({ - cv.Required(CONF_TOPIC): cv.publish_topic, - cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, - cv.Optional(CONF_RETAIN, default=True): cv.boolean, -}) - -MQTT_MESSAGE_TEMPLATE_SCHEMA = cv.Any(None, MQTT_MESSAGE_BASE, validate_message_just_topic) - -MQTT_MESSAGE_SCHEMA = cv.Any(None, MQTT_MESSAGE_BASE.extend({ - cv.Required(CONF_PAYLOAD): cv.mqtt_payload, -})) - -mqtt_ns = cg.esphome_ns.namespace('mqtt') -MQTTMessage = mqtt_ns.struct('MQTTMessage') -MQTTClientComponent = mqtt_ns.class_('MQTTClientComponent', cg.Component) -MQTTPublishAction = mqtt_ns.class_('MQTTPublishAction', automation.Action) -MQTTPublishJsonAction = mqtt_ns.class_('MQTTPublishJsonAction', automation.Action) -MQTTMessageTrigger = mqtt_ns.class_('MQTTMessageTrigger', - automation.Trigger.template(cg.std_string), - cg.Component) -MQTTJsonMessageTrigger = mqtt_ns.class_('MQTTJsonMessageTrigger', - automation.Trigger.template(cg.JsonObjectConstRef)) -MQTTComponent = mqtt_ns.class_('MQTTComponent', cg.Component) -MQTTConnectedCondition = mqtt_ns.class_('MQTTConnectedCondition', Condition) - -MQTTBinarySensorComponent = mqtt_ns.class_('MQTTBinarySensorComponent', MQTTComponent) -MQTTClimateComponent = mqtt_ns.class_('MQTTClimateComponent', MQTTComponent) -MQTTCoverComponent = mqtt_ns.class_('MQTTCoverComponent', MQTTComponent) -MQTTFanComponent = mqtt_ns.class_('MQTTFanComponent', MQTTComponent) -MQTTJSONLightComponent = mqtt_ns.class_('MQTTJSONLightComponent', MQTTComponent) -MQTTSensorComponent = mqtt_ns.class_('MQTTSensorComponent', MQTTComponent) -MQTTSwitchComponent = mqtt_ns.class_('MQTTSwitchComponent', MQTTComponent) -MQTTTextSensor = mqtt_ns.class_('MQTTTextSensor', MQTTComponent) +MQTT_MESSAGE_BASE = cv.Schema( + { + cv.Required(CONF_TOPIC): cv.publish_topic, + cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, + cv.Optional(CONF_RETAIN, default=True): cv.boolean, + } +) + +MQTT_MESSAGE_TEMPLATE_SCHEMA = cv.Any( + None, MQTT_MESSAGE_BASE, validate_message_just_topic +) + +MQTT_MESSAGE_SCHEMA = cv.Any( + None, + MQTT_MESSAGE_BASE.extend( + { + cv.Required(CONF_PAYLOAD): cv.mqtt_payload, + } + ), +) + +mqtt_ns = cg.esphome_ns.namespace("mqtt") +MQTTMessage = mqtt_ns.struct("MQTTMessage") +MQTTClientComponent = mqtt_ns.class_("MQTTClientComponent", cg.Component) +MQTTPublishAction = mqtt_ns.class_("MQTTPublishAction", automation.Action) +MQTTPublishJsonAction = mqtt_ns.class_("MQTTPublishJsonAction", automation.Action) +MQTTMessageTrigger = mqtt_ns.class_( + "MQTTMessageTrigger", automation.Trigger.template(cg.std_string), cg.Component +) +MQTTJsonMessageTrigger = mqtt_ns.class_( + "MQTTJsonMessageTrigger", automation.Trigger.template(cg.JsonObjectConstRef) +) +MQTTComponent = mqtt_ns.class_("MQTTComponent", cg.Component) +MQTTConnectedCondition = mqtt_ns.class_("MQTTConnectedCondition", Condition) + +MQTTBinarySensorComponent = mqtt_ns.class_("MQTTBinarySensorComponent", MQTTComponent) +MQTTClimateComponent = mqtt_ns.class_("MQTTClimateComponent", MQTTComponent) +MQTTCoverComponent = mqtt_ns.class_("MQTTCoverComponent", MQTTComponent) +MQTTFanComponent = mqtt_ns.class_("MQTTFanComponent", MQTTComponent) +MQTTJSONLightComponent = mqtt_ns.class_("MQTTJSONLightComponent", MQTTComponent) +MQTTSensorComponent = mqtt_ns.class_("MQTTSensorComponent", MQTTComponent) +MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent) +MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) def validate_config(value): @@ -64,28 +99,28 @@ def validate_config(value): topic_prefix = value[CONF_TOPIC_PREFIX] if CONF_BIRTH_MESSAGE not in value: out[CONF_BIRTH_MESSAGE] = { - CONF_TOPIC: f'{topic_prefix}/status', - CONF_PAYLOAD: 'online', + CONF_TOPIC: f"{topic_prefix}/status", + CONF_PAYLOAD: "online", CONF_QOS: 0, CONF_RETAIN: True, } if CONF_WILL_MESSAGE not in value: out[CONF_WILL_MESSAGE] = { - CONF_TOPIC: f'{topic_prefix}/status', - CONF_PAYLOAD: 'offline', + CONF_TOPIC: f"{topic_prefix}/status", + CONF_PAYLOAD: "offline", CONF_QOS: 0, CONF_RETAIN: True, } if CONF_SHUTDOWN_MESSAGE not in value: out[CONF_SHUTDOWN_MESSAGE] = { - CONF_TOPIC: f'{topic_prefix}/status', - CONF_PAYLOAD: 'offline', + CONF_TOPIC: f"{topic_prefix}/status", + CONF_PAYLOAD: "offline", CONF_QOS: 0, CONF_RETAIN: True, } if CONF_LOG_TOPIC not in value: out[CONF_LOG_TOPIC] = { - CONF_TOPIC: f'{topic_prefix}/debug', + CONF_TOPIC: f"{topic_prefix}/debug", CONF_QOS: 0, CONF_RETAIN: True, } @@ -94,46 +129,68 @@ def validate_config(value): def validate_fingerprint(value): value = cv.string(value) - if re.match(r'^[0-9a-f]{40}$', value) is None: + if re.match(r"^[0-9a-f]{40}$", value) is None: raise cv.Invalid("fingerprint must be valid SHA1 hash") return value -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(MQTTClientComponent), - cv.Required(CONF_BROKER): cv.string_strict, - cv.Optional(CONF_PORT, default=1883): cv.port, - cv.Optional(CONF_USERNAME, default=''): cv.string, - cv.Optional(CONF_PASSWORD, default=''): cv.string, - cv.Optional(CONF_CLIENT_ID): cv.string, - cv.Optional(CONF_DISCOVERY, default=True): cv.Any(cv.boolean, cv.one_of("CLEAN", upper=True)), - cv.Optional(CONF_DISCOVERY_RETAIN, default=True): cv.boolean, - cv.Optional(CONF_DISCOVERY_PREFIX, default="homeassistant"): cv.publish_topic, - - cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, - cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, - cv.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA, - cv.Optional(CONF_TOPIC_PREFIX, default=lambda: CORE.name): cv.publish_topic, - cv.Optional(CONF_LOG_TOPIC): cv.Any(None, MQTT_MESSAGE_BASE.extend({ - cv.Optional(CONF_LEVEL): logger.is_log_level, - }), validate_message_just_topic), - - cv.Optional(CONF_SSL_FINGERPRINTS): cv.All(cv.only_on_esp8266, - cv.ensure_list(validate_fingerprint)), - cv.Optional(CONF_KEEPALIVE, default='15s'): cv.positive_time_period_seconds, - cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_ON_MESSAGE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MQTTMessageTrigger), - cv.Required(CONF_TOPIC): cv.subscribe_topic, - cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, - cv.Optional(CONF_PAYLOAD): cv.string_strict, - }), - cv.Optional(CONF_ON_JSON_MESSAGE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MQTTJsonMessageTrigger), - cv.Required(CONF_TOPIC): cv.subscribe_topic, - cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, - }), -}), validate_config) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MQTTClientComponent), + cv.Required(CONF_BROKER): cv.string_strict, + cv.Optional(CONF_PORT, default=1883): cv.port, + cv.Optional(CONF_USERNAME, default=""): cv.string, + cv.Optional(CONF_PASSWORD, default=""): cv.string, + cv.Optional(CONF_CLIENT_ID): cv.string, + cv.Optional(CONF_DISCOVERY, default=True): cv.Any( + cv.boolean, cv.one_of("CLEAN", upper=True) + ), + cv.Optional(CONF_DISCOVERY_RETAIN, default=True): cv.boolean, + cv.Optional( + CONF_DISCOVERY_PREFIX, default="homeassistant" + ): cv.publish_topic, + cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, + cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, + cv.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA, + cv.Optional(CONF_TOPIC_PREFIX, default=lambda: CORE.name): cv.publish_topic, + cv.Optional(CONF_LOG_TOPIC): cv.Any( + None, + MQTT_MESSAGE_BASE.extend( + { + cv.Optional(CONF_LEVEL): logger.is_log_level, + } + ), + validate_message_just_topic, + ), + cv.Optional(CONF_SSL_FINGERPRINTS): cv.All( + cv.only_on_esp8266, cv.ensure_list(validate_fingerprint) + ), + cv.Optional(CONF_KEEPALIVE, default="15s"): cv.positive_time_period_seconds, + cv.Optional( + CONF_REBOOT_TIMEOUT, default="15min" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_ON_MESSAGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MQTTMessageTrigger), + cv.Required(CONF_TOPIC): cv.subscribe_topic, + cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, + cv.Optional(CONF_PAYLOAD): cv.string_strict, + } + ), + cv.Optional(CONF_ON_JSON_MESSAGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + MQTTJsonMessageTrigger + ), + cv.Required(CONF_TOPIC): cv.subscribe_topic, + cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, + } + ), + } + ), + validate_config, +) def exp_mqtt_message(config): @@ -141,10 +198,10 @@ def exp_mqtt_message(config): return cg.optional(cg.TemplateArguments(MQTTMessage)) exp = cg.StructInitializer( MQTTMessage, - ('topic', config[CONF_TOPIC]), - ('payload', config.get(CONF_PAYLOAD, "")), - ('qos', config[CONF_QOS]), - ('retain', config[CONF_RETAIN]) + ("topic", config[CONF_TOPIC]), + ("payload", config.get(CONF_PAYLOAD, "")), + ("qos", config[CONF_QOS]), + ("retain", config[CONF_RETAIN]), ) return exp @@ -155,8 +212,8 @@ def to_code(config): yield cg.register_component(var, config) # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json - cg.add_library('AsyncMqttClient-esphome', '0.8.4') - cg.add_define('USE_MQTT') + cg.add_library("AsyncMqttClient-esphome", "0.8.4") + cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) cg.add(var.set_broker_address(config[CONF_BROKER])) @@ -206,9 +263,12 @@ def to_code(config): if CONF_SSL_FINGERPRINTS in config: for fingerprint in config[CONF_SSL_FINGERPRINTS]: - arr = [cg.RawExpression("0x{}".format(fingerprint[i:i + 2])) for i in range(0, 40, 2)] + arr = [ + cg.RawExpression("0x{}".format(fingerprint[i : i + 2])) + for i in range(0, 40, 2) + ] cg.add(var.add_ssl_fingerprint(arr)) - cg.add_build_flag('-DASYNC_TCP_SSL_ENABLED=1') + cg.add_build_flag("-DASYNC_TCP_SSL_ENABLED=1") cg.add(var.set_keep_alive(config[CONF_KEEPALIVE])) @@ -220,23 +280,27 @@ def to_code(config): if CONF_PAYLOAD in conf: cg.add(trig.set_payload(conf[CONF_PAYLOAD])) yield cg.register_component(trig, conf) - yield automation.build_automation(trig, [(cg.std_string, 'x')], conf) + yield automation.build_automation(trig, [(cg.std_string, "x")], conf) for conf in config.get(CONF_ON_JSON_MESSAGE, []): trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC], conf[CONF_QOS]) - yield automation.build_automation(trig, [(cg.JsonObjectConstRef, 'x')], conf) + yield automation.build_automation(trig, [(cg.JsonObjectConstRef, "x")], conf) -MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.use_id(MQTTClientComponent), - cv.Required(CONF_TOPIC): cv.templatable(cv.publish_topic), - cv.Required(CONF_PAYLOAD): cv.templatable(cv.mqtt_payload), - cv.Optional(CONF_QOS, default=0): cv.templatable(cv.mqtt_qos), - cv.Optional(CONF_RETAIN, default=False): cv.templatable(cv.boolean), -}) +MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(MQTTClientComponent), + cv.Required(CONF_TOPIC): cv.templatable(cv.publish_topic), + cv.Required(CONF_PAYLOAD): cv.templatable(cv.mqtt_payload), + cv.Optional(CONF_QOS, default=0): cv.templatable(cv.mqtt_qos), + cv.Optional(CONF_RETAIN, default=False): cv.templatable(cv.boolean), + } +) -@automation.register_action('mqtt.publish', MQTTPublishAction, MQTT_PUBLISH_ACTION_SCHEMA) +@automation.register_action( + "mqtt.publish", MQTTPublishAction, MQTT_PUBLISH_ACTION_SCHEMA +) def mqtt_publish_action_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -252,24 +316,27 @@ def mqtt_publish_action_to_code(config, action_id, template_arg, args): yield var -MQTT_PUBLISH_JSON_ACTION_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.use_id(MQTTClientComponent), - cv.Required(CONF_TOPIC): cv.templatable(cv.publish_topic), - cv.Required(CONF_PAYLOAD): cv.lambda_, - cv.Optional(CONF_QOS, default=0): cv.templatable(cv.mqtt_qos), - cv.Optional(CONF_RETAIN, default=False): cv.templatable(cv.boolean), -}) +MQTT_PUBLISH_JSON_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(MQTTClientComponent), + cv.Required(CONF_TOPIC): cv.templatable(cv.publish_topic), + cv.Required(CONF_PAYLOAD): cv.lambda_, + cv.Optional(CONF_QOS, default=0): cv.templatable(cv.mqtt_qos), + cv.Optional(CONF_RETAIN, default=False): cv.templatable(cv.boolean), + } +) -@automation.register_action('mqtt.publish_json', MQTTPublishJsonAction, - MQTT_PUBLISH_JSON_ACTION_SCHEMA) +@automation.register_action( + "mqtt.publish_json", MQTTPublishJsonAction, MQTT_PUBLISH_JSON_ACTION_SCHEMA +) def mqtt_publish_json_action_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) template_ = yield cg.templatable(config[CONF_TOPIC], args, cg.std_string) cg.add(var.set_topic(template_)) - args_ = args + [(cg.JsonObjectRef, 'root')] + args_ = args + [(cg.JsonObjectRef, "root")] lambda_ = yield cg.process_lambda(config[CONF_PAYLOAD], args_, return_type=cg.void) cg.add(var.set_payload(lambda_)) template_ = yield cg.templatable(config[CONF_QOS], args, cg.uint8) @@ -280,10 +347,13 @@ def mqtt_publish_json_action_to_code(config, action_id, template_arg, args): def get_default_topic_for(data, component_type, name, suffix): - allowlist = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_' - sanitized_name = ''.join(x for x in name.lower().replace(' ', '_') if x in allowlist) - return '{}/{}/{}/{}'.format(data.topic_prefix, component_type, - sanitized_name, suffix) + allowlist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" + sanitized_name = "".join( + x for x in name.lower().replace(" ", "_") if x in allowlist + ) + return "{}/{}/{}/{}".format( + data.topic_prefix, component_type, sanitized_name, suffix + ) @coroutine @@ -303,14 +373,24 @@ def register_mqtt_component(var, config): if not availability: cg.add(var.disable_availability()) else: - cg.add(var.set_availability(availability[CONF_TOPIC], - availability[CONF_PAYLOAD_AVAILABLE], - availability[CONF_PAYLOAD_NOT_AVAILABLE])) - - -@automation.register_condition('mqtt.connected', MQTTConnectedCondition, cv.Schema({ - cv.GenerateID(): cv.use_id(MQTTClientComponent), -})) + cg.add( + var.set_availability( + availability[CONF_TOPIC], + availability[CONF_PAYLOAD_AVAILABLE], + availability[CONF_PAYLOAD_NOT_AVAILABLE], + ) + ) + + +@automation.register_condition( + "mqtt.connected", + MQTTConnectedCondition, + cv.Schema( + { + cv.GenerateID(): cv.use_id(MQTTClientComponent), + } + ), +) def mqtt_connected_to_code(config, condition_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(condition_id, template_arg, paren) diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index f115fe1bac75..c020d7310570 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #ifdef USE_FAN +#include "esphome/components/fan/fan_helpers.h" namespace esphome { namespace mqtt { @@ -94,9 +95,10 @@ bool MQTTFanComponent::publish_state() { this->state_->oscillating ? "oscillate_on" : "oscillate_off"); failed = failed || !success; } - if (this->state_->get_traits().supports_speed()) { + auto traits = this->state_->get_traits(); + if (traits.supports_speed()) { const char *payload; - switch (this->state_->speed) { + switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) { case FAN_SPEED_LOW: { payload = "low"; break; diff --git a/esphome/components/mqtt_subscribe/__init__.py b/esphome/components/mqtt_subscribe/__init__.py index 82e77f2f78e1..b17f32137271 100644 --- a/esphome/components/mqtt_subscribe/__init__.py +++ b/esphome/components/mqtt_subscribe/__init__.py @@ -1,3 +1,3 @@ import esphome.codegen as cg -mqtt_subscribe_ns = cg.esphome_ns.namespace('mqtt_subscribe') +mqtt_subscribe_ns = cg.esphome_ns.namespace("mqtt_subscribe") diff --git a/esphome/components/mqtt_subscribe/sensor/__init__.py b/esphome/components/mqtt_subscribe/sensor/__init__.py index dedc2ac9a780..00c2d324d37b 100644 --- a/esphome/components/mqtt_subscribe/sensor/__init__.py +++ b/esphome/components/mqtt_subscribe/sensor/__init__.py @@ -1,20 +1,35 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import mqtt, sensor -from esphome.const import CONF_ID, CONF_QOS, CONF_TOPIC, UNIT_EMPTY, ICON_EMPTY +from esphome.const import ( + CONF_ID, + CONF_QOS, + CONF_TOPIC, + UNIT_EMPTY, + ICON_EMPTY, + DEVICE_CLASS_EMPTY, +) from .. import mqtt_subscribe_ns -DEPENDENCIES = ['mqtt'] +DEPENDENCIES = ["mqtt"] -CONF_MQTT_PARENT_ID = 'mqtt_parent_id' -MQTTSubscribeSensor = mqtt_subscribe_ns.class_('MQTTSubscribeSensor', sensor.Sensor, cg.Component) +CONF_MQTT_PARENT_ID = "mqtt_parent_id" +MQTTSubscribeSensor = mqtt_subscribe_ns.class_( + "MQTTSubscribeSensor", sensor.Sensor, cg.Component +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1).extend({ - cv.GenerateID(): cv.declare_id(MQTTSubscribeSensor), - cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent), - cv.Required(CONF_TOPIC): cv.subscribe_topic, - cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(MQTTSubscribeSensor), + cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent), + cv.Required(CONF_TOPIC): cv.subscribe_topic, + cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/mqtt_subscribe/text_sensor/__init__.py b/esphome/components/mqtt_subscribe/text_sensor/__init__.py index c80909669b7a..b5f2c4307b1a 100644 --- a/esphome/components/mqtt_subscribe/text_sensor/__init__.py +++ b/esphome/components/mqtt_subscribe/text_sensor/__init__.py @@ -4,18 +4,21 @@ from esphome.const import CONF_ID, CONF_QOS, CONF_TOPIC from .. import mqtt_subscribe_ns -DEPENDENCIES = ['mqtt'] +DEPENDENCIES = ["mqtt"] -CONF_MQTT_PARENT_ID = 'mqtt_parent_id' -MQTTSubscribeTextSensor = mqtt_subscribe_ns.class_('MQTTSubscribeTextSensor', - text_sensor.TextSensor, cg.Component) +CONF_MQTT_PARENT_ID = "mqtt_parent_id" +MQTTSubscribeTextSensor = mqtt_subscribe_ns.class_( + "MQTTSubscribeTextSensor", text_sensor.TextSensor, cg.Component +) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(MQTTSubscribeTextSensor), - cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent), - cv.Required(CONF_TOPIC): cv.subscribe_topic, - cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MQTTSubscribeTextSensor), + cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent), + cv.Required(CONF_TOPIC): cv.subscribe_topic, + cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/ms5611/sensor.py b/esphome/components/ms5611/sensor.py index ab9aac6d5f8d..d180008140bb 100644 --- a/esphome/components/ms5611/sensor.py +++ b/esphome/components/ms5611/sensor.py @@ -1,20 +1,40 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, CONF_PRESSURE, \ - CONF_TEMPERATURE, ICON_THERMOMETER, UNIT_CELSIUS, ICON_GAUGE, \ - UNIT_HECTOPASCAL +from esphome.const import ( + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + ICON_GAUGE, + UNIT_HECTOPASCAL, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -ms5611_ns = cg.esphome_ns.namespace('ms5611') -MS5611Component = ms5611_ns.class_('MS5611Component', cg.PollingComponent, i2c.I2CDevice) +ms5611_ns = cg.esphome_ns.namespace("ms5611") +MS5611Component = ms5611_ns.class_( + "MS5611Component", cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(MS5611Component), - cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Required(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MS5611Component), + cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Required(CONF_PRESSURE): sensor.sensor_schema( + UNIT_HECTOPASCAL, ICON_GAUGE, 1, DEVICE_CLASS_PRESSURE + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x77)) +) def to_code(config): diff --git a/esphome/components/my9231/__init__.py b/esphome/components/my9231/__init__.py index 7ca0a30cab82..ed1edd7b2df9 100644 --- a/esphome/components/my9231/__init__.py +++ b/esphome/components/my9231/__init__.py @@ -1,22 +1,30 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.const import (CONF_BIT_DEPTH, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_ID, - CONF_NUM_CHANNELS, CONF_NUM_CHIPS) +from esphome.const import ( + CONF_BIT_DEPTH, + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_ID, + CONF_NUM_CHANNELS, + CONF_NUM_CHIPS, +) -AUTO_LOAD = ['output'] -my9231_ns = cg.esphome_ns.namespace('my9231') -MY9231OutputComponent = my9231_ns.class_('MY9231OutputComponent', cg.Component) +AUTO_LOAD = ["output"] +my9231_ns = cg.esphome_ns.namespace("my9231") +MY9231OutputComponent = my9231_ns.class_("MY9231OutputComponent", cg.Component) MULTI_CONF = True -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.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) +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.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) def to_code(config): diff --git a/esphome/components/my9231/output.py b/esphome/components/my9231/output.py index c69649fd5e18..30db84064d97 100644 --- a/esphome/components/my9231/output.py +++ b/esphome/components/my9231/output.py @@ -4,17 +4,18 @@ from esphome.const import CONF_CHANNEL, CONF_ID from . import MY9231OutputComponent -DEPENDENCIES = ['my9231'] +DEPENDENCIES = ["my9231"] -Channel = MY9231OutputComponent.class_('Channel', output.FloatOutput) +Channel = MY9231OutputComponent.class_("Channel", output.FloatOutput) -CONF_MY9231_ID = 'my9231_id' -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.uint16_t, -}).extend(cv.COMPONENT_SCHEMA) +CONF_MY9231_ID = "my9231_id" +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.uint16_t, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 32bfd951e9a2..0cf8057b8e28 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -2,32 +2,45 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import light -from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_METHOD, CONF_NUM_LEDS, CONF_PIN, \ - CONF_TYPE, CONF_VARIANT, CONF_OUTPUT_ID, CONF_INVERT +from esphome.const import ( + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_METHOD, + CONF_NUM_LEDS, + CONF_PIN, + CONF_TYPE, + CONF_VARIANT, + CONF_OUTPUT_ID, + CONF_INVERT, +) from esphome.core import CORE -neopixelbus_ns = cg.esphome_ns.namespace('neopixelbus') -NeoPixelBusLightOutputBase = neopixelbus_ns.class_('NeoPixelBusLightOutputBase', - light.AddressableLight) -NeoPixelRGBLightOutput = neopixelbus_ns.class_('NeoPixelRGBLightOutput', NeoPixelBusLightOutputBase) -NeoPixelRGBWLightOutput = neopixelbus_ns.class_('NeoPixelRGBWLightOutput', - NeoPixelBusLightOutputBase) -ESPNeoPixelOrder = neopixelbus_ns.namespace('ESPNeoPixelOrder') +neopixelbus_ns = cg.esphome_ns.namespace("neopixelbus") +NeoPixelBusLightOutputBase = neopixelbus_ns.class_( + "NeoPixelBusLightOutputBase", light.AddressableLight +) +NeoPixelRGBLightOutput = neopixelbus_ns.class_( + "NeoPixelRGBLightOutput", NeoPixelBusLightOutputBase +) +NeoPixelRGBWLightOutput = neopixelbus_ns.class_( + "NeoPixelRGBWLightOutput", NeoPixelBusLightOutputBase +) +ESPNeoPixelOrder = neopixelbus_ns.namespace("ESPNeoPixelOrder") NeoRgbFeature = cg.global_ns.NeoRgbFeature NeoRgbwFeature = cg.global_ns.NeoRgbwFeature def validate_type(value): value = cv.string(value).upper() - if 'R' not in value: + if "R" not in value: raise cv.Invalid("Must have R in type") - if 'G' not in value: + if "G" not in value: raise cv.Invalid("Must have G in type") - if 'B' not in value: + if "B" not in value: raise cv.Invalid("Must have B in type") - rest = set(value) - set('RGBW') + rest = set(value) - set("RGBW") if rest: - raise cv.Invalid("Type has invalid color: {}".format(', '.join(rest))) + raise cv.Invalid("Type has invalid color: {}".format(", ".join(rest))) if len(set(value)) != len(value): raise cv.Invalid("Type has duplicate color!") return value @@ -35,45 +48,45 @@ def validate_type(value): def validate_variant(value): value = cv.string(value).upper() - if value == 'WS2813': - value = 'WS2812X' - if value == 'WS2812': - value = '800KBPS' - if value == 'LC8812': - value = 'SK6812' + if value == "WS2813": + value = "WS2812X" + if value == "WS2812": + value = "800KBPS" + if value == "LC8812": + value = "SK6812" return cv.one_of(*VARIANTS)(value) def validate_method(value): if value is None: if CORE.is_esp32: - return 'ESP32_I2S_1' + return "ESP32_I2S_1" if CORE.is_esp8266: - return 'ESP8266_DMA' + return "ESP8266_DMA" raise NotImplementedError if CORE.is_esp32: - return cv.one_of(*ESP32_METHODS, upper=True, space='_')(value) + return cv.one_of(*ESP32_METHODS, upper=True, space="_")(value) if CORE.is_esp8266: - return cv.one_of(*ESP8266_METHODS, upper=True, space='_')(value) + return cv.one_of(*ESP8266_METHODS, upper=True, space="_")(value) raise NotImplementedError def validate_method_pin(value): method = value[CONF_METHOD] method_pins = { - 'ESP8266_DMA': [3], - 'ESP8266_UART0': [1], - 'ESP8266_ASYNC_UART0': [1], - 'ESP8266_UART1': [2], - 'ESP8266_ASYNC_UART1': [2], - 'ESP32_I2S_0': list(range(0, 32)), - 'ESP32_I2S_1': list(range(0, 32)), + "ESP8266_DMA": [3], + "ESP8266_UART0": [1], + "ESP8266_ASYNC_UART0": [1], + "ESP8266_UART1": [2], + "ESP8266_ASYNC_UART1": [2], + "ESP32_I2S_0": list(range(0, 32)), + "ESP32_I2S_1": list(range(0, 32)), } if CORE.is_esp8266: - method_pins['BIT_BANG'] = list(range(0, 16)) + method_pins["BIT_BANG"] = list(range(0, 16)) elif CORE.is_esp32: - method_pins['BIT_BANG'] = list(range(0, 32)) + method_pins["BIT_BANG"] = list(range(0, 32)) pins_ = method_pins.get(method) if pins_ is None: # all pins allowed for this method @@ -81,39 +94,42 @@ def validate_method_pin(value): for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN): if opt in value and value[opt] not in pins_: - raise cv.Invalid("Method {} only supports pin(s) {}".format( - method, ', '.join(f'GPIO{x}' for x in pins_) - ), path=[CONF_METHOD]) + raise cv.Invalid( + "Method {} only supports pin(s) {}".format( + method, ", ".join(f"GPIO{x}" for x in pins_) + ), + path=[CONF_METHOD], + ) return value VARIANTS = { - 'WS2812X': 'Ws2812x', - 'SK6812': 'Sk6812', - '800KBPS': '800Kbps', - '400KBPS': '400Kbps', + "WS2812X": "Ws2812x", + "SK6812": "Sk6812", + "800KBPS": "800Kbps", + "400KBPS": "400Kbps", } ESP8266_METHODS = { - 'ESP8266_DMA': 'NeoEsp8266Dma{}Method', - 'ESP8266_UART0': 'NeoEsp8266Uart0{}Method', - 'ESP8266_UART1': 'NeoEsp8266Uart1{}Method', - 'ESP8266_ASYNC_UART0': 'NeoEsp8266AsyncUart0{}Method', - 'ESP8266_ASYNC_UART1': 'NeoEsp8266AsyncUart1{}Method', - 'BIT_BANG': 'NeoEsp8266BitBang{}Method', + "ESP8266_DMA": "NeoEsp8266Dma{}Method", + "ESP8266_UART0": "NeoEsp8266Uart0{}Method", + "ESP8266_UART1": "NeoEsp8266Uart1{}Method", + "ESP8266_ASYNC_UART0": "NeoEsp8266AsyncUart0{}Method", + "ESP8266_ASYNC_UART1": "NeoEsp8266AsyncUart1{}Method", + "BIT_BANG": "NeoEsp8266BitBang{}Method", } ESP32_METHODS = { - 'ESP32_I2S_0': 'NeoEsp32I2s0{}Method', - 'ESP32_I2S_1': 'NeoEsp32I2s1{}Method', - 'ESP32_RMT_0': 'NeoEsp32Rmt0{}Method', - 'ESP32_RMT_1': 'NeoEsp32Rmt1{}Method', - 'ESP32_RMT_2': 'NeoEsp32Rmt2{}Method', - 'ESP32_RMT_3': 'NeoEsp32Rmt3{}Method', - 'ESP32_RMT_4': 'NeoEsp32Rmt4{}Method', - 'ESP32_RMT_5': 'NeoEsp32Rmt5{}Method', - 'ESP32_RMT_6': 'NeoEsp32Rmt6{}Method', - 'ESP32_RMT_7': 'NeoEsp32Rmt7{}Method', - 'BIT_BANG': 'NeoEsp32BitBang{}Method', + "ESP32_I2S_0": "NeoEsp32I2s0{}Method", + "ESP32_I2S_1": "NeoEsp32I2s1{}Method", + "ESP32_RMT_0": "NeoEsp32Rmt0{}Method", + "ESP32_RMT_1": "NeoEsp32Rmt1{}Method", + "ESP32_RMT_2": "NeoEsp32Rmt2{}Method", + "ESP32_RMT_3": "NeoEsp32Rmt3{}Method", + "ESP32_RMT_4": "NeoEsp32Rmt4{}Method", + "ESP32_RMT_5": "NeoEsp32Rmt5{}Method", + "ESP32_RMT_6": "NeoEsp32Rmt6{}Method", + "ESP32_RMT_7": "NeoEsp32Rmt7{}Method", + "BIT_BANG": "NeoEsp32BitBang{}Method", } @@ -122,10 +138,10 @@ def format_method(config): method = config[CONF_METHOD] if config[CONF_INVERT]: - if method == 'ESP8266_DMA': - variant = 'Inverted' + variant + if method == "ESP8266_DMA": + variant = "Inverted" + variant else: - variant += 'Inverted' + variant += "Inverted" if CORE.is_esp8266: return ESP8266_METHODS[method].format(variant) @@ -146,23 +162,27 @@ def validate(config): raise cv.Invalid("Must specify at least one of 'pin' or 'clock_pin'+'data_pin'") -CONFIG_SCHEMA = cv.All(light.ADDRESSABLE_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(NeoPixelBusLightOutputBase), - - cv.Optional(CONF_TYPE, default='GRB'): validate_type, - cv.Optional(CONF_VARIANT, default='800KBPS'): validate_variant, - cv.Optional(CONF_METHOD, default=None): validate_method, - cv.Optional(CONF_INVERT, default='no'): cv.boolean, - cv.Optional(CONF_PIN): pins.output_pin, - cv.Optional(CONF_CLOCK_PIN): pins.output_pin, - cv.Optional(CONF_DATA_PIN): pins.output_pin, - - cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, -}).extend(cv.COMPONENT_SCHEMA), validate, validate_method_pin) +CONFIG_SCHEMA = cv.All( + light.ADDRESSABLE_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(NeoPixelBusLightOutputBase), + cv.Optional(CONF_TYPE, default="GRB"): validate_type, + cv.Optional(CONF_VARIANT, default="800KBPS"): validate_variant, + cv.Optional(CONF_METHOD, default=None): validate_method, + cv.Optional(CONF_INVERT, default="no"): cv.boolean, + cv.Optional(CONF_PIN): pins.output_pin, + cv.Optional(CONF_CLOCK_PIN): pins.output_pin, + cv.Optional(CONF_DATA_PIN): pins.output_pin, + cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, + } + ).extend(cv.COMPONENT_SCHEMA), + validate, + validate_method_pin, +) def to_code(config): - has_white = 'W' in config[CONF_TYPE] + has_white = "W" in config[CONF_TYPE] template = cg.TemplateArguments(getattr(cg.global_ns, format_method(config))) if has_white: out_type = NeoPixelRGBWLightOutput.template(template) @@ -176,9 +196,13 @@ def to_code(config): if CONF_PIN in config: cg.add(var.add_leds(config[CONF_NUM_LEDS], config[CONF_PIN])) else: - cg.add(var.add_leds(config[CONF_NUM_LEDS], config[CONF_CLOCK_PIN], config[CONF_DATA_PIN])) + cg.add( + var.add_leds( + config[CONF_NUM_LEDS], config[CONF_CLOCK_PIN], config[CONF_DATA_PIN] + ) + ) cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE]))) # https://github.com/Makuna/NeoPixelBus/blob/master/library.json - cg.add_library('NeoPixelBus-esphome', '2.5.7') + cg.add_library("NeoPixelBus-esphome", "2.5.7") diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 5e8097187ec2..46601d8345e5 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/color.h" #include "esphome/components/light/light_output.h" #include "esphome/components/light/addressable_light.h" @@ -73,7 +74,7 @@ class NeoPixelBusLightOutputBase : public light::AddressableLight { // ========== INTERNAL METHODS ========== void setup() override { for (int i = 0; i < this->size(); i++) { - (*this)[i] = light::ESPColor(0, 0, 0, 0); + (*this)[i] = Color(0, 0, 0, 0); } this->effect_data_ = new uint8_t[this->size()]; diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index a380e32bfe33..46713d3ffe99 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -1,2 +1,2 @@ # Dummy package to allow components to depend on network -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index 1d90c924961e..67a49df9fa60 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -1,3 +1,3 @@ import esphome.codegen as cg -nextion_ns = cg.esphome_ns.namespace('nextion') +nextion_ns = cg.esphome_ns.namespace("nextion") diff --git a/esphome/components/nextion/binary_sensor.py b/esphome/components/nextion/binary_sensor.py index 6003c5980389..e822b65eb55b 100644 --- a/esphome/components/nextion/binary_sensor.py +++ b/esphome/components/nextion/binary_sensor.py @@ -5,19 +5,22 @@ from . import nextion_ns from .display import Nextion -DEPENDENCIES = ['display'] - -CONF_NEXTION_ID = 'nextion_id' - -NextionTouchComponent = nextion_ns.class_('NextionTouchComponent', binary_sensor.BinarySensor) - -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(NextionTouchComponent), - cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion), - - cv.Required(CONF_PAGE_ID): cv.uint8_t, - cv.Required(CONF_COMPONENT_ID): cv.uint8_t, -}) +DEPENDENCIES = ["display"] + +CONF_NEXTION_ID = "nextion_id" + +NextionTouchComponent = nextion_ns.class_( + "NextionTouchComponent", binary_sensor.BinarySensor +) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionTouchComponent), + cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion), + cv.Required(CONF_PAGE_ID): cv.uint8_t, + cv.Required(CONF_COMPONENT_ID): cv.uint8_t, + } +) def to_code(config): diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 394de6958592..483395fe2f8f 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -4,16 +4,22 @@ from esphome.const import CONF_ID, CONF_LAMBDA, CONF_BRIGHTNESS from . import nextion_ns -DEPENDENCIES = ['uart'] -AUTO_LOAD = ['binary_sensor'] +DEPENDENCIES = ["uart"] +AUTO_LOAD = ["binary_sensor"] -Nextion = nextion_ns.class_('Nextion', cg.PollingComponent, uart.UARTDevice) -NextionRef = Nextion.operator('ref') +Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) +NextionRef = Nextion.operator("ref") -CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(Nextion), - cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, -}).extend(cv.polling_component_schema('5s')).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Nextion), + cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + } + ) + .extend(cv.polling_component_schema("5s")) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): @@ -24,8 +30,9 @@ def to_code(config): if CONF_BRIGHTNESS in config: cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(NextionRef, 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], [(NextionRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) yield display.register_display(var, config) diff --git a/esphome/components/nfc/__init__.py b/esphome/components/nfc/__init__.py index ae3c9a4c0ad6..b795a5d5ca5e 100644 --- a/esphome/components/nfc/__init__.py +++ b/esphome/components/nfc/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg -CODEOWNERS = ['@jesserockz'] +CODEOWNERS = ["@jesserockz"] -nfc_ns = cg.esphome_ns.namespace('nfc') +nfc_ns = cg.esphome_ns.namespace("nfc") -NfcTag = nfc_ns.class_('NfcTag') +NfcTag = nfc_ns.class_("NfcTag") diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py index f2eb601ed226..f3505eec53ad 100644 --- a/esphome/components/ntc/sensor.py +++ b/esphome/components/ntc/sensor.py @@ -3,37 +3,50 @@ import esphome.config_validation as cv import esphome.codegen as cg from esphome.components import sensor -from esphome.const import CONF_CALIBRATION, CONF_ID, CONF_REFERENCE_RESISTANCE, \ - CONF_REFERENCE_TEMPERATURE, CONF_SENSOR, CONF_TEMPERATURE, CONF_VALUE, ICON_THERMOMETER, \ - UNIT_CELSIUS - -ntc_ns = cg.esphome_ns.namespace('ntc') -NTC = ntc_ns.class_('NTC', cg.Component, sensor.Sensor) - -CONF_B_CONSTANT = 'b_constant' -CONF_A = 'a' -CONF_B = 'b' -CONF_C = 'c' +from esphome.const import ( + CONF_CALIBRATION, + CONF_ID, + CONF_REFERENCE_RESISTANCE, + CONF_REFERENCE_TEMPERATURE, + CONF_SENSOR, + CONF_TEMPERATURE, + CONF_VALUE, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, +) + +ntc_ns = cg.esphome_ns.namespace("ntc") +NTC = ntc_ns.class_("NTC", cg.Component, sensor.Sensor) + +CONF_B_CONSTANT = "b_constant" +CONF_A = "a" +CONF_B = "b" +CONF_C = "c" ZERO_POINT = 273.15 def validate_calibration_parameter(value): if isinstance(value, dict): - return cv.Schema({ - cv.Required(CONF_TEMPERATURE): cv.float_, - cv.Required(CONF_VALUE): cv.float_, - })(value) + return cv.Schema( + { + cv.Required(CONF_TEMPERATURE): cv.float_, + cv.Required(CONF_VALUE): cv.float_, + } + )(value) value = cv.string(value) - parts = value.split('->') + parts = value.split("->") if len(parts) != 2: raise cv.Invalid("Calibration parameter must be of form 3000 -> 23°C") voltage = cv.resistance(parts[0].strip()) temperature = cv.temperature(parts[1].strip()) - return validate_calibration_parameter({ - CONF_TEMPERATURE: temperature, - CONF_VALUE: voltage, - }) + return validate_calibration_parameter( + { + CONF_TEMPERATURE: temperature, + CONF_VALUE: voltage, + } + ) def calc_steinhart_hart(value): @@ -48,16 +61,16 @@ def calc_steinhart_hart(value): l2 = log(r2) l3 = log(r3) - y1 = 1/t1 - y2 = 1/t2 - y3 = 1/t3 + y1 = 1 / t1 + y2 = 1 / t2 + y3 = 1 / t3 - g2 = (y2-y1)/(l2-l1) - g3 = (y3-y1)/(l3-l1) + g2 = (y2 - y1) / (l2 - l1) + g3 = (y3 - y1) / (l3 - l1) - c = (g3-g2)/(l3-l2) * 1/(l1+l2+l3) - b = g2 - c*(l1*l1 + l1*l2 + l2*l2) - a = y1 - (b + l1*l1*c) * l1 + c = (g3 - g2) / (l3 - l2) * 1 / (l1 + l2 + l3) + b = g2 - c * (l1 * l1 + l1 * l2 + l2 * l2) + a = y1 - (b + l1 * l1 * c) * l1 return a, b, c @@ -66,8 +79,8 @@ def calc_b(value): t0 = value[CONF_REFERENCE_TEMPERATURE] + ZERO_POINT r0 = value[CONF_REFERENCE_RESISTANCE] - a = (1/t0) - (1/beta) * log(r0) - b = 1/beta + a = (1 / t0) - (1 / beta) * log(r0) + b = 1 / beta c = 0 return a, b, c @@ -75,21 +88,27 @@ def calc_b(value): def process_calibration(value): if isinstance(value, dict): - value = cv.Schema({ - cv.Required(CONF_B_CONSTANT): cv.float_, - cv.Required(CONF_REFERENCE_TEMPERATURE): cv.temperature, - cv.Required(CONF_REFERENCE_RESISTANCE): cv.resistance, - })(value) + value = cv.Schema( + { + cv.Required(CONF_B_CONSTANT): cv.float_, + cv.Required(CONF_REFERENCE_TEMPERATURE): cv.temperature, + cv.Required(CONF_REFERENCE_RESISTANCE): cv.resistance, + } + )(value) a, b, c = calc_b(value) elif isinstance(value, list): if len(value) != 3: - raise cv.Invalid("Steinhart–Hart Calibration must consist of exactly three values") + raise cv.Invalid( + "Steinhart–Hart Calibration must consist of exactly three values" + ) value = cv.Schema([validate_calibration_parameter])(value) a, b, c = calc_steinhart_hart(value) else: - raise cv.Invalid("Calibration parameter accepts either a list for steinhart-hart " - "calibration, or mapping for b-constant calibration, " - "not {}".format(type(value))) + raise cv.Invalid( + "Calibration parameter accepts either a list for steinhart-hart " + "calibration, or mapping for b-constant calibration, " + "not {}".format(type(value)) + ) return { CONF_A: a, @@ -98,11 +117,17 @@ def process_calibration(value): } -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.GenerateID(): cv.declare_id(NTC), - cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), - cv.Required(CONF_CALIBRATION): process_calibration, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE) + .extend( + { + cv.GenerateID(): cv.declare_id(NTC), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_CALIBRATION): process_calibration, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 8956227c1732..25a278f5bf16 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -2,25 +2,33 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( - CONF_ID, CONF_NUM_ATTEMPTS, CONF_PASSWORD, - CONF_PORT, CONF_REBOOT_TIMEOUT, CONF_SAFE_MODE + CONF_ID, + CONF_NUM_ATTEMPTS, + CONF_PASSWORD, + CONF_PORT, + CONF_REBOOT_TIMEOUT, + CONF_SAFE_MODE, ) from esphome.core import CORE, coroutine_with_priority -CODEOWNERS = ['@esphome/core'] -DEPENDENCIES = ['network'] +CODEOWNERS = ["@esphome/core"] +DEPENDENCIES = ["network"] -ota_ns = cg.esphome_ns.namespace('ota') -OTAComponent = ota_ns.class_('OTAComponent', cg.Component) +ota_ns = cg.esphome_ns.namespace("ota") +OTAComponent = ota_ns.class_("OTAComponent", cg.Component) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(OTAComponent), - cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, - cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port, - cv.Optional(CONF_PASSWORD, default=''): cv.string, - cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_NUM_ATTEMPTS, default='10'): cv.positive_not_null_int -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(OTAComponent), + cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, + cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port, + cv.Optional(CONF_PASSWORD, default=""): cv.string, + cv.Optional( + CONF_REBOOT_TIMEOUT, default="5min" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_NUM_ATTEMPTS, default="10"): cv.positive_not_null_int, + } +).extend(cv.COMPONENT_SCHEMA) @coroutine_with_priority(50.0) @@ -32,11 +40,12 @@ def to_code(config): yield cg.register_component(var, config) if config[CONF_SAFE_MODE]: - condition = var.should_enter_safe_mode(config[CONF_NUM_ATTEMPTS], - config[CONF_REBOOT_TIMEOUT]) + condition = var.should_enter_safe_mode( + config[CONF_NUM_ATTEMPTS], config[CONF_REBOOT_TIMEOUT] + ) cg.add(RawExpression(f"if ({condition}) return")) if CORE.is_esp8266: - cg.add_library('Update', None) + cg.add_library("Update", None) elif CORE.is_esp32: - cg.add_library('Hash', None) + cg.add_library("Hash", None) diff --git a/esphome/components/output/__init__.py b/esphome/components/output/__init__.py index 34cb7c3f7a9f..487bd8cba5ac 100644 --- a/esphome/components/output/__init__.py +++ b/esphome/components/output/__init__.py @@ -3,34 +3,44 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components import power_supply -from esphome.const import CONF_ID, CONF_INVERTED, CONF_LEVEL, CONF_MAX_POWER, \ - CONF_MIN_POWER, CONF_POWER_SUPPLY +from esphome.const import ( + CONF_ID, + CONF_INVERTED, + CONF_LEVEL, + CONF_MAX_POWER, + CONF_MIN_POWER, + CONF_POWER_SUPPLY, +) from esphome.core import CORE, coroutine -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True -BINARY_OUTPUT_SCHEMA = cv.Schema({ - cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply), - cv.Optional(CONF_INVERTED): cv.boolean, -}) - -FLOAT_OUTPUT_SCHEMA = BINARY_OUTPUT_SCHEMA.extend({ - cv.Optional(CONF_MAX_POWER): cv.percentage, - cv.Optional(CONF_MIN_POWER): cv.percentage, -}) - -output_ns = cg.esphome_ns.namespace('output') -BinaryOutput = output_ns.class_('BinaryOutput') -BinaryOutputPtr = BinaryOutput.operator('ptr') -FloatOutput = output_ns.class_('FloatOutput', BinaryOutput) -FloatOutputPtr = FloatOutput.operator('ptr') +BINARY_OUTPUT_SCHEMA = cv.Schema( + { + cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply), + cv.Optional(CONF_INVERTED): cv.boolean, + } +) + +FLOAT_OUTPUT_SCHEMA = BINARY_OUTPUT_SCHEMA.extend( + { + cv.Optional(CONF_MAX_POWER): cv.percentage, + cv.Optional(CONF_MIN_POWER): cv.percentage, + } +) + +output_ns = cg.esphome_ns.namespace("output") +BinaryOutput = output_ns.class_("BinaryOutput") +BinaryOutputPtr = BinaryOutput.operator("ptr") +FloatOutput = output_ns.class_("FloatOutput", BinaryOutput) +FloatOutputPtr = FloatOutput.operator("ptr") # Actions -TurnOffAction = output_ns.class_('TurnOffAction', automation.Action) -TurnOnAction = output_ns.class_('TurnOnAction', automation.Action) -SetLevelAction = output_ns.class_('SetLevelAction', automation.Action) +TurnOffAction = output_ns.class_("TurnOffAction", automation.Action) +TurnOnAction = output_ns.class_("TurnOnAction", automation.Action) +SetLevelAction = output_ns.class_("SetLevelAction", automation.Action) @coroutine @@ -53,27 +63,37 @@ def register_output(var, config): yield setup_output_platform_(var, config) -BINARY_OUTPUT_ACTION_SCHEMA = maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(BinaryOutput), -}) +BINARY_OUTPUT_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(BinaryOutput), + } +) -@automation.register_action('output.turn_on', TurnOnAction, BINARY_OUTPUT_ACTION_SCHEMA) +@automation.register_action("output.turn_on", TurnOnAction, BINARY_OUTPUT_ACTION_SCHEMA) def output_turn_on_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_action('output.turn_off', TurnOffAction, BINARY_OUTPUT_ACTION_SCHEMA) +@automation.register_action( + "output.turn_off", TurnOffAction, BINARY_OUTPUT_ACTION_SCHEMA +) def output_turn_off_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_action('output.set_level', SetLevelAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(FloatOutput), - cv.Required(CONF_LEVEL): cv.templatable(cv.percentage), -})) +@automation.register_action( + "output.set_level", + SetLevelAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(FloatOutput), + cv.Required(CONF_LEVEL): cv.templatable(cv.percentage), + } + ), +) def output_set_level_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/output/switch/__init__.py b/esphome/components/output/switch/__init__.py index 5795271f8b3b..14027de74cd5 100644 --- a/esphome/components/output/switch/__init__.py +++ b/esphome/components/output/switch/__init__.py @@ -4,12 +4,14 @@ from esphome.const import CONF_ID, CONF_OUTPUT from .. import output_ns -OutputSwitch = output_ns.class_('OutputSwitch', switch.Switch, cg.Component) +OutputSwitch = output_ns.class_("OutputSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(OutputSwitch), - cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(OutputSwitch), + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 55dfe35e34ec..8c5c9a014477 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -4,7 +4,6 @@ def _merge_package(full_old, full_new): - def merge(old, new): # pylint: disable=no-else-return if isinstance(new, dict): @@ -30,8 +29,10 @@ def do_packages_pass(config: dict): packages = config[CONF_PACKAGES] with cv.prepend_path(CONF_PACKAGES): if not isinstance(packages, dict): - raise cv.Invalid("Packages must be a key to value mapping, got {} instead" - "".format(type(packages))) + raise cv.Invalid( + "Packages must be a key to value mapping, got {} instead" + "".format(type(packages)) + ) for package_name, package_config in packages.items(): with cv.prepend_path(package_name): diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index ba1059e36b54..202481b93605 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -3,34 +3,49 @@ from esphome.components import light from esphome.const import CONF_FROM, CONF_ID, CONF_SEGMENTS, CONF_TO, CONF_OUTPUT_ID -partitions_ns = cg.esphome_ns.namespace('partition') -AddressableSegment = partitions_ns.class_('AddressableSegment') -PartitionLightOutput = partitions_ns.class_('PartitionLightOutput', light.AddressableLight) +partitions_ns = cg.esphome_ns.namespace("partition") +AddressableSegment = partitions_ns.class_("AddressableSegment") +PartitionLightOutput = partitions_ns.class_( + "PartitionLightOutput", light.AddressableLight +) def validate_from_to(value): if value[CONF_FROM] > value[CONF_TO]: - raise cv.Invalid("From ({}) must not be larger than to ({})" - "".format(value[CONF_FROM], value[CONF_TO])) + raise cv.Invalid( + "From ({}) must not be larger than to ({})" + "".format(value[CONF_FROM], value[CONF_TO]) + ) return value -CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(PartitionLightOutput), - cv.Required(CONF_SEGMENTS): cv.All(cv.ensure_list({ - cv.Required(CONF_ID): cv.use_id(light.AddressableLightState), - cv.Required(CONF_FROM): cv.positive_int, - cv.Required(CONF_TO): cv.positive_int, - }, validate_from_to), cv.Length(min=1)), -}) +CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(PartitionLightOutput), + cv.Required(CONF_SEGMENTS): cv.All( + cv.ensure_list( + { + cv.Required(CONF_ID): cv.use_id(light.AddressableLightState), + cv.Required(CONF_FROM): cv.positive_int, + cv.Required(CONF_TO): cv.positive_int, + }, + validate_from_to, + ), + cv.Length(min=1), + ), + } +) def to_code(config): segments = [] for conf in config[CONF_SEGMENTS]: var = yield cg.get_variable(conf[CONF_ID]) - segments.append(AddressableSegment(var, conf[CONF_FROM], - conf[CONF_TO] - conf[CONF_FROM] + 1)) + segments.append( + AddressableSegment( + var, conf[CONF_FROM], conf[CONF_TO] - conf[CONF_FROM] + 1 + ) + ) var = cg.new_Pvariable(config[CONF_OUTPUT_ID], segments) yield cg.register_component(var, config) diff --git a/esphome/components/pca9685/__init__.py b/esphome/components/pca9685/__init__.py index 8e02bd78dfcf..d88012a1cd9a 100644 --- a/esphome/components/pca9685/__init__.py +++ b/esphome/components/pca9685/__init__.py @@ -3,17 +3,24 @@ from esphome.components import i2c from esphome.const import CONF_FREQUENCY, CONF_ID -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] MULTI_CONF = True -pca9685_ns = cg.esphome_ns.namespace('pca9685') -PCA9685Output = pca9685_ns.class_('PCA9685Output', cg.Component, i2c.I2CDevice) +pca9685_ns = cg.esphome_ns.namespace("pca9685") +PCA9685Output = pca9685_ns.class_("PCA9685Output", cg.Component, i2c.I2CDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(PCA9685Output), - cv.Required(CONF_FREQUENCY): cv.All(cv.frequency, - cv.Range(min=23.84, max=1525.88)), -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x40)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PCA9685Output), + cv.Required(CONF_FREQUENCY): cv.All( + cv.frequency, cv.Range(min=23.84, max=1525.88) + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x40)) +) def to_code(config): diff --git a/esphome/components/pca9685/output.py b/esphome/components/pca9685/output.py index b5f480561120..c3cb88eeaf60 100644 --- a/esphome/components/pca9685/output.py +++ b/esphome/components/pca9685/output.py @@ -4,17 +4,18 @@ from esphome.const import CONF_CHANNEL, CONF_ID from . import PCA9685Output, pca9685_ns -DEPENDENCIES = ['pca9685'] +DEPENDENCIES = ["pca9685"] -PCA9685Channel = pca9685_ns.class_('PCA9685Channel', output.FloatOutput) -CONF_PCA9685_ID = 'pca9685_id' +PCA9685Channel = pca9685_ns.class_("PCA9685Channel", output.FloatOutput) +CONF_PCA9685_ID = "pca9685_id" -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.int_range(min=0, max=15), -}) +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.int_range(min=0, max=15), + } +) def to_code(config): diff --git a/esphome/components/pcd8544/display.py b/esphome/components/pcd8544/display.py index f4b625fe8bb7..50cc1b02a8c6 100644 --- a/esphome/components/pcd8544/display.py +++ b/esphome/components/pcd8544/display.py @@ -3,23 +3,37 @@ from esphome import pins from esphome.components import display, spi from esphome.const import ( - CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES, CONF_RESET_PIN, CONF_CS_PIN, CONF_CONTRAST + CONF_DC_PIN, + CONF_ID, + CONF_LAMBDA, + CONF_PAGES, + CONF_RESET_PIN, + CONF_CS_PIN, + CONF_CONTRAST, ) -DEPENDENCIES = ['spi'] +DEPENDENCIES = ["spi"] -pcd8544_ns = cg.esphome_ns.namespace('pcd8544') -PCD8544 = pcd8544_ns.class_('PCD8544', cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice) +pcd8544_ns = cg.esphome_ns.namespace("pcd8544") +PCD8544 = pcd8544_ns.class_( + "PCD8544", cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice +) -CONFIG_SCHEMA = cv.All(display.FULL_DISPLAY_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(PCD8544), - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, # CE - cv.Optional(CONF_CONTRAST, default=0x7f): cv.int_, -}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema()), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PCD8544), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, # CE + cv.Optional(CONF_CONTRAST, default=0x7F): cv.int_, + } + ) + .extend(cv.polling_component_schema("1s")) + .extend(spi.spi_device_schema()), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): @@ -37,6 +51,7 @@ def to_code(config): cg.add(var.set_contrast(config[CONF_CONTRAST])) if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/pcf8574/__init__.py b/esphome/components/pcf8574/__init__.py index daf367c08918..8c028fabc154 100644 --- a/esphome/components/pcf8574/__init__.py +++ b/esphome/components/pcf8574/__init__.py @@ -4,25 +4,31 @@ from esphome.components import i2c from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] MULTI_CONF = True -pcf8574_ns = cg.esphome_ns.namespace('pcf8574') -PCF8574GPIOMode = pcf8574_ns.enum('PCF8574GPIOMode') +pcf8574_ns = cg.esphome_ns.namespace("pcf8574") +PCF8574GPIOMode = pcf8574_ns.enum("PCF8574GPIOMode") PCF8674_GPIO_MODES = { - 'INPUT': PCF8574GPIOMode.PCF8574_INPUT, - 'OUTPUT': PCF8574GPIOMode.PCF8574_OUTPUT, + "INPUT": PCF8574GPIOMode.PCF8574_INPUT, + "OUTPUT": PCF8574GPIOMode.PCF8574_OUTPUT, } -PCF8574Component = pcf8574_ns.class_('PCF8574Component', cg.Component, i2c.I2CDevice) -PCF8574GPIOPin = pcf8574_ns.class_('PCF8574GPIOPin', cg.GPIOPin) +PCF8574Component = pcf8574_ns.class_("PCF8574Component", cg.Component, i2c.I2CDevice) +PCF8574GPIOPin = pcf8574_ns.class_("PCF8574GPIOPin", cg.GPIOPin) -CONF_PCF8574 = 'pcf8574' -CONF_PCF8575 = 'pcf8575' -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(PCF8574Component), - cv.Optional(CONF_PCF8575, default=False): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x21)) +CONF_PCF8574 = "pcf8574" +CONF_PCF8575 = "pcf8575" +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(PCF8574Component), + cv.Optional(CONF_PCF8575, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x21)) +) def to_code(config): @@ -34,27 +40,37 @@ def to_code(config): def validate_pcf8574_gpio_mode(value): value = cv.string(value) - if value.upper() == 'INPUT_PULLUP': - raise cv.Invalid("INPUT_PULLUP mode has been removed in 1.14 and been combined into " - "INPUT mode (they were the same thing). Please use INPUT instead.") + if value.upper() == "INPUT_PULLUP": + raise cv.Invalid( + "INPUT_PULLUP mode has been removed in 1.14 and been combined into " + "INPUT mode (they were the same thing). Please use INPUT instead." + ) return cv.enum(PCF8674_GPIO_MODES, upper=True)(value) -PCF8574_OUTPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_PCF8574): cv.use_id(PCF8574Component), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): validate_pcf8574_gpio_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) -PCF8574_INPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_PCF8574): cv.use_id(PCF8574Component), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): validate_pcf8574_gpio_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) +PCF8574_OUTPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_PCF8574): cv.use_id(PCF8574Component), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="OUTPUT"): validate_pcf8574_gpio_mode, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) +PCF8574_INPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_PCF8574): cv.use_id(PCF8574Component), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="INPUT"): validate_pcf8574_gpio_mode, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) -@pins.PIN_SCHEMA_REGISTRY.register('pcf8574', (PCF8574_OUTPUT_PIN_SCHEMA, PCF8574_INPUT_PIN_SCHEMA)) +@pins.PIN_SCHEMA_REGISTRY.register( + "pcf8574", (PCF8574_OUTPUT_PIN_SCHEMA, PCF8574_INPUT_PIN_SCHEMA) +) def pcf8574_pin_to_code(config): parent = yield cg.get_variable(config[CONF_PCF8574]) - yield PCF8574GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED]) + yield PCF8574GPIOPin.new( + parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED] + ) diff --git a/esphome/components/pid/__init__.py b/esphome/components/pid/__init__.py index 6f14e1003304..71a87b6ae508 100644 --- a/esphome/components/pid/__init__.py +++ b/esphome/components/pid/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@OttoWinter'] +CODEOWNERS = ["@OttoWinter"] diff --git a/esphome/components/pid/climate.py b/esphome/components/pid/climate.py index 446c614f14f6..c16f1726ae84 100644 --- a/esphome/components/pid/climate.py +++ b/esphome/components/pid/climate.py @@ -4,40 +4,51 @@ from esphome.components import climate, sensor, output from esphome.const import CONF_ID, CONF_SENSOR -pid_ns = cg.esphome_ns.namespace('pid') -PIDClimate = pid_ns.class_('PIDClimate', climate.Climate, cg.Component) -PIDAutotuneAction = pid_ns.class_('PIDAutotuneAction', automation.Action) -PIDResetIntegralTermAction = pid_ns.class_('PIDResetIntegralTermAction', automation.Action) -PIDSetControlParametersAction = pid_ns.class_('PIDSetControlParametersAction', automation.Action) - -CONF_DEFAULT_TARGET_TEMPERATURE = 'default_target_temperature' - -CONF_KP = 'kp' -CONF_KI = 'ki' -CONF_KD = 'kd' -CONF_CONTROL_PARAMETERS = 'control_parameters' -CONF_COOL_OUTPUT = 'cool_output' -CONF_HEAT_OUTPUT = 'heat_output' -CONF_NOISEBAND = 'noiseband' -CONF_POSITIVE_OUTPUT = 'positive_output' -CONF_NEGATIVE_OUTPUT = 'negative_output' -CONF_MIN_INTEGRAL = 'min_integral' -CONF_MAX_INTEGRAL = 'max_integral' - -CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(PIDClimate), - cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), - cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE): cv.temperature, - cv.Optional(CONF_COOL_OUTPUT): cv.use_id(output.FloatOutput), - cv.Optional(CONF_HEAT_OUTPUT): cv.use_id(output.FloatOutput), - cv.Required(CONF_CONTROL_PARAMETERS): cv.Schema({ - cv.Required(CONF_KP): cv.float_, - cv.Optional(CONF_KI, default=0.0): cv.float_, - cv.Optional(CONF_KD, default=0.0): cv.float_, - cv.Optional(CONF_MIN_INTEGRAL, default=-1): cv.float_, - cv.Optional(CONF_MAX_INTEGRAL, default=1): cv.float_, - }), -}), cv.has_at_least_one_key(CONF_COOL_OUTPUT, CONF_HEAT_OUTPUT)) +pid_ns = cg.esphome_ns.namespace("pid") +PIDClimate = pid_ns.class_("PIDClimate", climate.Climate, cg.Component) +PIDAutotuneAction = pid_ns.class_("PIDAutotuneAction", automation.Action) +PIDResetIntegralTermAction = pid_ns.class_( + "PIDResetIntegralTermAction", automation.Action +) +PIDSetControlParametersAction = pid_ns.class_( + "PIDSetControlParametersAction", automation.Action +) + +CONF_DEFAULT_TARGET_TEMPERATURE = "default_target_temperature" + +CONF_KP = "kp" +CONF_KI = "ki" +CONF_KD = "kd" +CONF_CONTROL_PARAMETERS = "control_parameters" +CONF_COOL_OUTPUT = "cool_output" +CONF_HEAT_OUTPUT = "heat_output" +CONF_NOISEBAND = "noiseband" +CONF_POSITIVE_OUTPUT = "positive_output" +CONF_NEGATIVE_OUTPUT = "negative_output" +CONF_MIN_INTEGRAL = "min_integral" +CONF_MAX_INTEGRAL = "max_integral" + +CONFIG_SCHEMA = cv.All( + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PIDClimate), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE): cv.temperature, + cv.Optional(CONF_COOL_OUTPUT): cv.use_id(output.FloatOutput), + cv.Optional(CONF_HEAT_OUTPUT): cv.use_id(output.FloatOutput), + cv.Required(CONF_CONTROL_PARAMETERS): cv.Schema( + { + cv.Required(CONF_KP): cv.float_, + cv.Optional(CONF_KI, default=0.0): cv.float_, + cv.Optional(CONF_KD, default=0.0): cv.float_, + cv.Optional(CONF_MIN_INTEGRAL, default=-1): cv.float_, + cv.Optional(CONF_MAX_INTEGRAL, default=1): cv.float_, + } + ), + } + ), + cv.has_at_least_one_key(CONF_COOL_OUTPUT, CONF_HEAT_OUTPUT), +) def to_code(config): @@ -67,23 +78,35 @@ def to_code(config): @automation.register_action( - 'climate.pid.reset_integral_term', + "climate.pid.reset_integral_term", PIDResetIntegralTermAction, - automation.maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(PIDClimate), - }) + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(PIDClimate), + } + ), ) def pid_reset_integral_term(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_action('climate.pid.autotune', PIDAutotuneAction, automation.maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(PIDClimate), - cv.Optional(CONF_NOISEBAND, default=0.25): cv.float_, - cv.Optional(CONF_POSITIVE_OUTPUT, default=1.0): cv.possibly_negative_percentage, - cv.Optional(CONF_NEGATIVE_OUTPUT, default=-1.0): cv.possibly_negative_percentage, -})) +@automation.register_action( + "climate.pid.autotune", + PIDAutotuneAction, + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(PIDClimate), + cv.Optional(CONF_NOISEBAND, default=0.25): cv.float_, + cv.Optional( + CONF_POSITIVE_OUTPUT, default=1.0 + ): cv.possibly_negative_percentage, + cv.Optional( + CONF_NEGATIVE_OUTPUT, default=-1.0 + ): cv.possibly_negative_percentage, + } + ), +) def esp8266_set_frequency_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -94,14 +117,16 @@ def esp8266_set_frequency_to_code(config, action_id, template_arg, args): @automation.register_action( - 'climate.pid.set_control_parameters', + "climate.pid.set_control_parameters", PIDSetControlParametersAction, - automation.maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(PIDClimate), - cv.Required(CONF_KP): cv.templatable(cv.float_), - cv.Optional(CONF_KI, default=0.0): cv.templatable(cv.float_), - cv.Optional(CONF_KD, default=0.0): cv.templatable(cv.float_), - }) + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(PIDClimate), + cv.Required(CONF_KP): cv.templatable(cv.float_), + cv.Optional(CONF_KI, default=0.0): cv.templatable(cv.float_), + cv.Optional(CONF_KD, default=0.0): cv.templatable(cv.float_), + } + ), ) def set_control_parameters(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) diff --git a/esphome/components/pid/sensor/__init__.py b/esphome/components/pid/sensor/__init__.py index ff8cf15eb5f8..d29fb6b66297 100644 --- a/esphome/components/pid/sensor/__init__.py +++ b/esphome/components/pid/sensor/__init__.py @@ -1,32 +1,43 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import CONF_ID, UNIT_PERCENT, ICON_GAUGE, CONF_TYPE +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_EMPTY, + UNIT_PERCENT, + ICON_GAUGE, + CONF_TYPE, +) from ..climate import pid_ns, PIDClimate -PIDClimateSensor = pid_ns.class_('PIDClimateSensor', sensor.Sensor, cg.Component) -PIDClimateSensorType = pid_ns.enum('PIDClimateSensorType') +PIDClimateSensor = pid_ns.class_("PIDClimateSensor", sensor.Sensor, cg.Component) +PIDClimateSensorType = pid_ns.enum("PIDClimateSensorType") PID_CLIMATE_SENSOR_TYPES = { - 'RESULT': PIDClimateSensorType.PID_SENSOR_TYPE_RESULT, - 'ERROR': PIDClimateSensorType.PID_SENSOR_TYPE_ERROR, - 'PROPORTIONAL': PIDClimateSensorType.PID_SENSOR_TYPE_PROPORTIONAL, - 'INTEGRAL': PIDClimateSensorType.PID_SENSOR_TYPE_INTEGRAL, - 'DERIVATIVE': PIDClimateSensorType.PID_SENSOR_TYPE_DERIVATIVE, - 'HEAT': PIDClimateSensorType.PID_SENSOR_TYPE_HEAT, - 'COOL': PIDClimateSensorType.PID_SENSOR_TYPE_COOL, - 'KP': PIDClimateSensorType.PID_SENSOR_TYPE_KP, - 'KI': PIDClimateSensorType.PID_SENSOR_TYPE_KI, - 'KD': PIDClimateSensorType.PID_SENSOR_TYPE_KD, + "RESULT": PIDClimateSensorType.PID_SENSOR_TYPE_RESULT, + "ERROR": PIDClimateSensorType.PID_SENSOR_TYPE_ERROR, + "PROPORTIONAL": PIDClimateSensorType.PID_SENSOR_TYPE_PROPORTIONAL, + "INTEGRAL": PIDClimateSensorType.PID_SENSOR_TYPE_INTEGRAL, + "DERIVATIVE": PIDClimateSensorType.PID_SENSOR_TYPE_DERIVATIVE, + "HEAT": PIDClimateSensorType.PID_SENSOR_TYPE_HEAT, + "COOL": PIDClimateSensorType.PID_SENSOR_TYPE_COOL, + "KP": PIDClimateSensorType.PID_SENSOR_TYPE_KP, + "KI": PIDClimateSensorType.PID_SENSOR_TYPE_KI, + "KD": PIDClimateSensorType.PID_SENSOR_TYPE_KD, } -CONF_CLIMATE_ID = 'climate_id' -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_GAUGE, 1).extend({ - cv.GenerateID(): cv.declare_id(PIDClimateSensor), - cv.GenerateID(CONF_CLIMATE_ID): cv.use_id(PIDClimate), - - cv.Required(CONF_TYPE): cv.enum(PID_CLIMATE_SENSOR_TYPES, upper=True), -}).extend(cv.COMPONENT_SCHEMA) +CONF_CLIMATE_ID = "climate_id" +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_PERCENT, ICON_GAUGE, 1, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(PIDClimateSensor), + cv.GenerateID(CONF_CLIMATE_ID): cv.use_id(PIDClimate), + cv.Required(CONF_TYPE): cv.enum(PID_CLIMATE_SENSOR_TYPES, upper=True), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index 4dbe500d3d03..05becfb71f39 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -1,22 +1,36 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, uart -from esphome.const import CONF_FORMALDEHYDE, CONF_HUMIDITY, CONF_ID, CONF_PM_10_0, \ - CONF_PM_1_0, CONF_PM_2_5, CONF_TEMPERATURE, CONF_TYPE, ICON_CHEMICAL_WEAPON, \ - UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_CELSIUS, \ - UNIT_PERCENT - -DEPENDENCIES = ['uart'] - -pmsx003_ns = cg.esphome_ns.namespace('pmsx003') -PMSX003Component = pmsx003_ns.class_('PMSX003Component', uart.UARTDevice, cg.Component) -PMSX003Sensor = pmsx003_ns.class_('PMSX003Sensor', sensor.Sensor) - -TYPE_PMSX003 = 'PMSX003' -TYPE_PMS5003T = 'PMS5003T' -TYPE_PMS5003ST = 'PMS5003ST' - -PMSX003Type = pmsx003_ns.enum('PMSX003Type') +from esphome.const import ( + CONF_FORMALDEHYDE, + CONF_HUMIDITY, + CONF_ID, + CONF_PM_10_0, + CONF_PM_1_0, + CONF_PM_2_5, + CONF_TEMPERATURE, + CONF_TYPE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_CHEMICAL_WEAPON, + ICON_EMPTY, + UNIT_MICROGRAMS_PER_CUBIC_METER, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +DEPENDENCIES = ["uart"] + +pmsx003_ns = cg.esphome_ns.namespace("pmsx003") +PMSX003Component = pmsx003_ns.class_("PMSX003Component", uart.UARTDevice, cg.Component) +PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor) + +TYPE_PMSX003 = "PMSX003" +TYPE_PMS5003T = "PMS5003T" +TYPE_PMS5003ST = "PMS5003ST" + +PMSX003Type = pmsx003_ns.enum("PMSX003Type") PMSX003_TYPES = { TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003, TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T, @@ -36,27 +50,52 @@ def validate_pmsx003_sensors(value): for key, types in SENSORS_TO_TYPE.items(): if key in value and value[CONF_TYPE] not in types: - raise cv.Invalid("{} does not have {} sensor!".format(value[CONF_TYPE], key)) + raise cv.Invalid( + "{} does not have {} sensor!".format(value[CONF_TYPE], key) + ) return value -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(PMSX003Component), - cv.Required(CONF_TYPE): cv.enum(PMSX003_TYPES, upper=True), - - cv.Optional(CONF_PM_1_0): - sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), - cv.Optional(CONF_PM_2_5): - sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), - cv.Optional(CONF_PM_10_0): - sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), - cv.Optional(CONF_TEMPERATURE): - sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): - sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), - cv.Optional(CONF_FORMALDEHYDE): - sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0), -}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PMSX003Component), + cv.Required(CONF_TYPE): cv.enum(PMSX003_TYPES, upper=True), + cv.Optional(CONF_PM_1_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_2_5): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_10_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_FORMALDEHYDE): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): diff --git a/esphome/components/pn532/__init__.py b/esphome/components/pn532/__init__.py index c96ebe2b4dea..5403ebe5cd6b 100644 --- a/esphome/components/pn532/__init__.py +++ b/esphome/components/pn532/__init__.py @@ -5,38 +5,52 @@ from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID from esphome.core import coroutine -CODEOWNERS = ['@OttoWinter', '@jesserockz'] -AUTO_LOAD = ['binary_sensor', 'nfc'] +CODEOWNERS = ["@OttoWinter", "@jesserockz"] +AUTO_LOAD = ["binary_sensor", "nfc"] MULTI_CONF = True -CONF_PN532_ID = 'pn532_id' -CONF_ON_FINISHED_WRITE = 'on_finished_write' +CONF_PN532_ID = "pn532_id" +CONF_ON_FINISHED_WRITE = "on_finished_write" -pn532_ns = cg.esphome_ns.namespace('pn532') -PN532 = pn532_ns.class_('PN532', cg.PollingComponent) +pn532_ns = cg.esphome_ns.namespace("pn532") +PN532 = pn532_ns.class_("PN532", cg.PollingComponent) -PN532OnTagTrigger = pn532_ns.class_('PN532OnTagTrigger', - automation.Trigger.template(cg.std_string, nfc.NfcTag)) -PN532OnFinishedWriteTrigger = pn532_ns.class_('PN532OnFinishedWriteTrigger', - automation.Trigger.template()) +PN532OnTagTrigger = pn532_ns.class_( + "PN532OnTagTrigger", automation.Trigger.template(cg.std_string, nfc.NfcTag) +) +PN532OnFinishedWriteTrigger = pn532_ns.class_( + "PN532OnFinishedWriteTrigger", automation.Trigger.template() +) -PN532IsWritingCondition = pn532_ns.class_('PN532IsWritingCondition', automation.Condition) +PN532IsWritingCondition = pn532_ns.class_( + "PN532IsWritingCondition", automation.Condition +) -PN532_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(PN532), - cv.Optional(CONF_ON_TAG): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger), - }), - cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnFinishedWriteTrigger), - }), -}).extend(cv.polling_component_schema('1s')) +PN532_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(PN532), + cv.Optional(CONF_ON_TAG): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger), + } + ), + cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + PN532OnFinishedWriteTrigger + ), + } + ), + } +).extend(cv.polling_component_schema("1s")) def CONFIG_SCHEMA(conf): if conf: - raise cv.Invalid("This component has been moved in 1.16, please see the docs for updated " - "instructions. https://esphome.io/components/binary_sensor/pn532.html") + raise cv.Invalid( + "This component has been moved in 1.16, please see the docs for updated " + "instructions. https://esphome.io/components/binary_sensor/pn532.html" + ) @coroutine @@ -46,17 +60,24 @@ def setup_pn532(var, config): for conf in config.get(CONF_ON_TAG, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) cg.add(var.register_trigger(trigger)) - yield automation.build_automation(trigger, [(cg.std_string, 'x'), (nfc.NfcTag, 'tag')], - conf) + yield automation.build_automation( + trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf + ) for conf in config.get(CONF_ON_FINISHED_WRITE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) yield automation.build_automation(trigger, [], conf) -@automation.register_condition('pn532.is_writing', PN532IsWritingCondition, cv.Schema({ - cv.GenerateID(): cv.use_id(PN532), -})) +@automation.register_condition( + "pn532.is_writing", + PN532IsWritingCondition, + cv.Schema( + { + cv.GenerateID(): cv.use_id(PN532), + } + ), +) def pn532_is_writing_to_code(config, condition_id, template_arg, args): var = cg.new_Pvariable(condition_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/pn532/binary_sensor.py b/esphome/components/pn532/binary_sensor.py index 2404bc9e99fc..5e6bf6c2b927 100644 --- a/esphome/components/pn532/binary_sensor.py +++ b/esphome/components/pn532/binary_sensor.py @@ -5,31 +5,39 @@ from esphome.core import HexInt from . import pn532_ns, PN532, CONF_PN532_ID -DEPENDENCIES = ['pn532'] +DEPENDENCIES = ["pn532"] def validate_uid(value): value = cv.string_strict(value) - for x in value.split('-'): + for x in value.split("-"): if len(x) != 2: - raise cv.Invalid("Each part (separated by '-') of the UID must be two characters " - "long.") + raise cv.Invalid( + "Each part (separated by '-') of the UID must be two characters " + "long." + ) try: x = int(x, 16) except ValueError as err: - raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.") from err + raise cv.Invalid( + "Valid characters for parts of a UID are 0123456789ABCDEF." + ) from err if x < 0 or x > 255: - raise cv.Invalid("Valid values for UID parts (separated by '-') are 00 to FF") + raise cv.Invalid( + "Valid values for UID parts (separated by '-') are 00 to FF" + ) return value -PN532BinarySensor = pn532_ns.class_('PN532BinarySensor', binary_sensor.BinarySensor) +PN532BinarySensor = pn532_ns.class_("PN532BinarySensor", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(PN532BinarySensor), - cv.GenerateID(CONF_PN532_ID): cv.use_id(PN532), - cv.Required(CONF_UID): validate_uid, -}) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PN532BinarySensor), + cv.GenerateID(CONF_PN532_ID): cv.use_id(PN532), + cv.Required(CONF_UID): validate_uid, + } +) def to_code(config): @@ -38,5 +46,5 @@ def to_code(config): hub = yield cg.get_variable(config[CONF_PN532_ID]) cg.add(hub.register_tag(var)) - addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')] + addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")] cg.add(var.set_uid(addr)) diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index bd62806c1ef1..09d94c42b10b 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -17,12 +17,12 @@ nfc::NfcTag *PN532::read_mifare_ultralight_tag_(std::vector &uid) { if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); } + ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index); if (message_length == 0) { return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); } std::vector data; - uint8_t index = 0; for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { std::vector page_data; if (!this->read_mifare_ultralight_page_(page, page_data)) { @@ -31,13 +31,12 @@ nfc::NfcTag *PN532::read_mifare_ultralight_tag_(std::vector &uid) { } data.insert(data.end(), page_data.begin(), page_data.end()); - if (index >= (message_length + message_start_index)) + if (data.size() >= (message_length + message_start_index)) break; - - index += page_data.size(); } data.erase(data.begin(), data.begin() + message_start_index); + data.erase(data.begin() + message_length, data.end()); return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2, data); } diff --git a/esphome/components/pn532_i2c/__init__.py b/esphome/components/pn532_i2c/__init__.py index f1c50adf4583..80995bd01865 100644 --- a/esphome/components/pn532_i2c/__init__.py +++ b/esphome/components/pn532_i2c/__init__.py @@ -3,16 +3,20 @@ from esphome.components import i2c, pn532 from esphome.const import CONF_ID -AUTO_LOAD = ['pn532'] -CODEOWNERS = ['@OttoWinter', '@jesserockz'] -DEPENDENCIES = ['i2c'] +AUTO_LOAD = ["pn532"] +CODEOWNERS = ["@OttoWinter", "@jesserockz"] +DEPENDENCIES = ["i2c"] -pn532_i2c_ns = cg.esphome_ns.namespace('pn532_i2c') -PN532I2C = pn532_i2c_ns.class_('PN532I2C', pn532.PN532, i2c.I2CDevice) +pn532_i2c_ns = cg.esphome_ns.namespace("pn532_i2c") +PN532I2C = pn532_i2c_ns.class_("PN532I2C", pn532.PN532, i2c.I2CDevice) -CONFIG_SCHEMA = cv.All(pn532.PN532_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(PN532I2C), -}).extend(i2c.i2c_device_schema(0x24))) +CONFIG_SCHEMA = cv.All( + pn532.PN532_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PN532I2C), + } + ).extend(i2c.i2c_device_schema(0x24)) +) def to_code(config): diff --git a/esphome/components/pn532_spi/__init__.py b/esphome/components/pn532_spi/__init__.py index e378b96c2daa..2abe43629116 100644 --- a/esphome/components/pn532_spi/__init__.py +++ b/esphome/components/pn532_spi/__init__.py @@ -3,16 +3,20 @@ from esphome.components import spi, pn532 from esphome.const import CONF_ID -AUTO_LOAD = ['pn532'] -CODEOWNERS = ['@OttoWinter', '@jesserockz'] -DEPENDENCIES = ['spi'] +AUTO_LOAD = ["pn532"] +CODEOWNERS = ["@OttoWinter", "@jesserockz"] +DEPENDENCIES = ["spi"] -pn532_spi_ns = cg.esphome_ns.namespace('pn532_spi') -PN532Spi = pn532_spi_ns.class_('PN532Spi', pn532.PN532, spi.SPIDevice) +pn532_spi_ns = cg.esphome_ns.namespace("pn532_spi") +PN532Spi = pn532_spi_ns.class_("PN532Spi", pn532.PN532, spi.SPIDevice) -CONFIG_SCHEMA = cv.All(pn532.PN532_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(PN532Spi), -}).extend(spi.spi_device_schema(cs_pin_required=True))) +CONFIG_SCHEMA = cv.All( + pn532.PN532_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PN532Spi), + } + ).extend(spi.spi_device_schema(cs_pin_required=True)) +) def to_code(config): diff --git a/esphome/components/power_supply/__init__.py b/esphome/components/power_supply/__init__.py index d50278863761..efed5e0d8128 100644 --- a/esphome/components/power_supply/__init__.py +++ b/esphome/components/power_supply/__init__.py @@ -3,17 +3,23 @@ from esphome import pins from esphome.const import CONF_ENABLE_TIME, CONF_ID, CONF_KEEP_ON_TIME, CONF_PIN -CODEOWNERS = ['@esphome/core'] -power_supply_ns = cg.esphome_ns.namespace('power_supply') -PowerSupply = power_supply_ns.class_('PowerSupply', cg.Component) +CODEOWNERS = ["@esphome/core"] +power_supply_ns = cg.esphome_ns.namespace("power_supply") +PowerSupply = power_supply_ns.class_("PowerSupply", cg.Component) MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(PowerSupply), - cv.Required(CONF_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_ENABLE_TIME, default='20ms'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_KEEP_ON_TIME, default='10s'): cv.positive_time_period_milliseconds, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(PowerSupply), + cv.Required(CONF_PIN): pins.gpio_output_pin_schema, + cv.Optional( + CONF_ENABLE_TIME, default="20ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_KEEP_ON_TIME, default="10s" + ): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -25,4 +31,4 @@ def to_code(config): cg.add(var.set_enable_time(config[CONF_ENABLE_TIME])) cg.add(var.set_keep_on_time(config[CONF_KEEP_ON_TIME])) - cg.add_define('USE_POWER_SUPPLY') + cg.add_define("USE_POWER_SUPPLY") diff --git a/esphome/components/prometheus/__init__.py b/esphome/components/prometheus/__init__.py index 9c3deef73da5..a1b376c763ca 100644 --- a/esphome/components/prometheus/__init__.py +++ b/esphome/components/prometheus/__init__.py @@ -4,21 +4,25 @@ from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.components import web_server_base -AUTO_LOAD = ['web_server_base'] +AUTO_LOAD = ["web_server_base"] -prometheus_ns = cg.esphome_ns.namespace('prometheus') -PrometheusHandler = prometheus_ns.class_('PrometheusHandler', cg.Component) +prometheus_ns = cg.esphome_ns.namespace("prometheus") +PrometheusHandler = prometheus_ns.class_("PrometheusHandler", cg.Component) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(PrometheusHandler), - cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(PrometheusHandler), + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( + web_server_base.WebServerBase + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): paren = yield cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) - cg.add_define('USE_PROMETHEUS') + cg.add_define("USE_PROMETHEUS") var = cg.new_Pvariable(config[CONF_ID], paren) yield cg.register_component(var, config) diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index 7550d5693a91..0dff2959e8e2 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -2,23 +2,35 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor -from esphome.const import CONF_COUNT_MODE, CONF_FALLING_EDGE, CONF_ID, CONF_INTERNAL_FILTER, \ - CONF_PIN, CONF_RISING_EDGE, CONF_NUMBER, CONF_TOTAL, \ - ICON_PULSE, UNIT_PULSES_PER_MINUTE, UNIT_PULSES +from esphome.const import ( + CONF_COUNT_MODE, + CONF_FALLING_EDGE, + CONF_ID, + CONF_INTERNAL_FILTER, + CONF_PIN, + CONF_RISING_EDGE, + CONF_NUMBER, + CONF_TOTAL, + DEVICE_CLASS_EMPTY, + ICON_PULSE, + UNIT_PULSES_PER_MINUTE, + UNIT_PULSES, +) from esphome.core import CORE -pulse_counter_ns = cg.esphome_ns.namespace('pulse_counter') -PulseCounterCountMode = pulse_counter_ns.enum('PulseCounterCountMode') +pulse_counter_ns = cg.esphome_ns.namespace("pulse_counter") +PulseCounterCountMode = pulse_counter_ns.enum("PulseCounterCountMode") COUNT_MODES = { - 'DISABLE': PulseCounterCountMode.PULSE_COUNTER_DISABLE, - 'INCREMENT': PulseCounterCountMode.PULSE_COUNTER_INCREMENT, - 'DECREMENT': PulseCounterCountMode.PULSE_COUNTER_DECREMENT, + "DISABLE": PulseCounterCountMode.PULSE_COUNTER_DISABLE, + "INCREMENT": PulseCounterCountMode.PULSE_COUNTER_INCREMENT, + "DECREMENT": PulseCounterCountMode.PULSE_COUNTER_DECREMENT, } COUNT_MODE_SCHEMA = cv.enum(COUNT_MODES, upper=True) -PulseCounterSensor = pulse_counter_ns.class_('PulseCounterSensor', - sensor.Sensor, cg.PollingComponent) +PulseCounterSensor = pulse_counter_ns.class_( + "PulseCounterSensor", sensor.Sensor, cg.PollingComponent +) def validate_internal_filter(value): @@ -34,33 +46,52 @@ def validate_internal_filter(value): def validate_pulse_counter_pin(value): value = pins.internal_gpio_input_pin_schema(value) if CORE.is_esp8266 and value[CONF_NUMBER] >= 16: - raise cv.Invalid("Pins GPIO16 and GPIO17 cannot be used as pulse counters on ESP8266.") + raise cv.Invalid( + "Pins GPIO16 and GPIO17 cannot be used as pulse counters on ESP8266." + ) return value def validate_count_mode(value): rising_edge = value[CONF_RISING_EDGE] falling_edge = value[CONF_FALLING_EDGE] - if rising_edge == 'DISABLE' and falling_edge == 'DISABLE': - raise cv.Invalid("Can't set both count modes to DISABLE! This means no counting occurs at " - "all!") + if rising_edge == "DISABLE" and falling_edge == "DISABLE": + raise cv.Invalid( + "Can't set both count modes to DISABLE! This means no counting occurs at " + "all!" + ) return value -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PULSES_PER_MINUTE, ICON_PULSE, 2).extend({ - cv.GenerateID(): cv.declare_id(PulseCounterSensor), - cv.Required(CONF_PIN): validate_pulse_counter_pin, - cv.Optional(CONF_COUNT_MODE, default={ - CONF_RISING_EDGE: 'INCREMENT', - CONF_FALLING_EDGE: 'DISABLE', - }): cv.All(cv.Schema({ - cv.Required(CONF_RISING_EDGE): COUNT_MODE_SCHEMA, - cv.Required(CONF_FALLING_EDGE): COUNT_MODE_SCHEMA, - }), validate_count_mode), - cv.Optional(CONF_INTERNAL_FILTER, default='13us'): validate_internal_filter, - cv.Optional(CONF_TOTAL): sensor.sensor_schema(UNIT_PULSES, ICON_PULSE, 0), - -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_PULSES_PER_MINUTE, ICON_PULSE, 2, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(PulseCounterSensor), + cv.Required(CONF_PIN): validate_pulse_counter_pin, + cv.Optional( + CONF_COUNT_MODE, + default={ + CONF_RISING_EDGE: "INCREMENT", + CONF_FALLING_EDGE: "DISABLE", + }, + ): cv.All( + cv.Schema( + { + cv.Required(CONF_RISING_EDGE): COUNT_MODE_SCHEMA, + cv.Required(CONF_FALLING_EDGE): COUNT_MODE_SCHEMA, + } + ), + validate_count_mode, + ), + cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, + cv.Optional(CONF_TOTAL): sensor.sensor_schema( + UNIT_PULSES, ICON_PULSE, 0, DEVICE_CLASS_EMPTY + ), + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): diff --git a/esphome/components/pulse_meter/__init__.py b/esphome/components/pulse_meter/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp new file mode 100644 index 000000000000..8cf341c8a13a --- /dev/null +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -0,0 +1,87 @@ +#include "pulse_meter_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pulse_meter { + +static const char *TAG = "pulse_meter"; + +void PulseMeterSensor::setup() { + this->pin_->setup(); + this->isr_pin_ = pin_->to_isr(); + this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, CHANGE); + + this->last_detected_edge_us_ = 0; + this->last_valid_edge_us_ = 0; +} + +void PulseMeterSensor::loop() { + const uint32_t now = micros(); + + // If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until + // we get at least two valid pulses. + const uint32_t time_since_valid_edge_us = now - this->last_valid_edge_us_; + if ((this->last_valid_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_)) { + ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000); + this->last_detected_edge_us_ = 0; + this->last_valid_edge_us_ = 0; + this->pulse_width_us_ = 0; + } + + // We quantize our pulse widths to 1 ms to avoid unnecessary jitter + const uint32_t pulse_width_ms = this->pulse_width_us_ / 1000; + if (this->pulse_width_dedupe_.next(pulse_width_ms)) { + if (pulse_width_ms == 0) { + // Treat 0 pulse width as 0 pulses/min (normally because we've not detected any pulses for a while) + this->publish_state(0); + } else { + // Calculate pulses/min from the pulse width in ms + this->publish_state((60.0 * 1000.0) / pulse_width_ms); + } + } + + if (this->total_sensor_ != nullptr) { + const uint32_t total = this->total_pulses_; + if (this->total_dedupe_.next(total)) { + this->total_sensor_->publish_state(total); + } + } +} + +void PulseMeterSensor::dump_config() { + LOG_SENSOR("", "Pulse Meter", this); + LOG_PIN(" Pin: ", this->pin_); + ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %u µs", this->filter_us_); + ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000); +} + +void ICACHE_RAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { + // This is an interrupt handler - we can't call any virtual method from this method + + // Get the current time before we do anything else so the measurements are consistent + const uint32_t now = micros(); + + // We only look at rising edges + if (!sensor->isr_pin_->digital_read()) { + return; + } + + // Ignore the first detected pulse (we need at least two pulses to measure the width) + if (sensor->last_detected_edge_us_ != 0) { + // Check to see if we should filter this edge out + if ((now - sensor->last_detected_edge_us_) >= sensor->filter_us_) { + // Don't measure the first valid pulse (we need at least two pulses to measure the width) + if (sensor->last_valid_edge_us_ != 0) { + sensor->pulse_width_us_ = (now - sensor->last_valid_edge_us_); + } + + sensor->total_pulses_++; + sensor->last_valid_edge_us_ = now; + } + } + + sensor->last_detected_edge_us_ = now; +} + +} // namespace pulse_meter +} // namespace esphome diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h new file mode 100644 index 000000000000..7d3adbbbcb20 --- /dev/null +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -0,0 +1,42 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/esphal.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace pulse_meter { + +class PulseMeterSensor : public sensor::Sensor, public Component { + public: + void set_pin(GPIOPin *pin) { this->pin_ = pin; } + void set_filter_us(uint32_t filter) { this->filter_us_ = filter; } + void set_timeout_us(uint32_t timeout) { this->timeout_us_ = timeout; } + void set_total_sensor(sensor::Sensor *sensor) { this->total_sensor_ = sensor; } + + void setup() override; + void loop() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void dump_config() override; + + protected: + static void gpio_intr(PulseMeterSensor *sensor); + + GPIOPin *pin_ = nullptr; + ISRInternalGPIOPin *isr_pin_; + uint32_t filter_us_ = 0; + uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; + sensor::Sensor *total_sensor_ = nullptr; + + Deduplicator pulse_width_dedupe_; + Deduplicator total_dedupe_; + + volatile uint32_t last_detected_edge_us_ = 0; + volatile uint32_t last_valid_edge_us_ = 0; + volatile uint32_t pulse_width_us_ = 0; + volatile uint32_t total_pulses_ = 0; +}; + +} // namespace pulse_meter +} // namespace esphome diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py new file mode 100644 index 000000000000..37827b735ddd --- /dev/null +++ b/esphome/components/pulse_meter/sensor.py @@ -0,0 +1,75 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import sensor +from esphome.const import ( + CONF_ID, + CONF_INTERNAL_FILTER, + CONF_PIN, + CONF_NUMBER, + CONF_TIMEOUT, + CONF_TOTAL, + ICON_PULSE, + UNIT_PULSES, + UNIT_PULSES_PER_MINUTE, + DEVICE_CLASS_EMPTY, +) +from esphome.core import CORE + +CODEOWNERS = ["@stevebaxter"] + +pulse_meter_ns = cg.esphome_ns.namespace("pulse_meter") + +PulseMeterSensor = pulse_meter_ns.class_( + "PulseMeterSensor", sensor.Sensor, cg.Component +) + + +def validate_internal_filter(value): + return cv.positive_time_period_microseconds(value) + + +def validate_timeout(value): + value = cv.positive_time_period_microseconds(value) + if value.total_minutes > 70: + raise cv.Invalid("Maximum timeout is 70 minutes") + return value + + +def validate_pulse_meter_pin(value): + value = pins.internal_gpio_input_pin_schema(value) + if CORE.is_esp8266 and value[CONF_NUMBER] >= 16: + raise cv.Invalid( + "Pins GPIO16 and GPIO17 cannot be used as pulse counters on ESP8266." + ) + return value + + +CONFIG_SCHEMA = sensor.sensor_schema( + UNIT_PULSES_PER_MINUTE, ICON_PULSE, 2, DEVICE_CLASS_EMPTY +).extend( + { + cv.GenerateID(): cv.declare_id(PulseMeterSensor), + cv.Required(CONF_PIN): validate_pulse_meter_pin, + cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, + cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout, + cv.Optional(CONF_TOTAL): sensor.sensor_schema( + UNIT_PULSES, ICON_PULSE, 0, DEVICE_CLASS_EMPTY + ), + } +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield sensor.register_sensor(var, config) + + pin = yield cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) + cg.add(var.set_filter_us(config[CONF_INTERNAL_FILTER])) + cg.add(var.set_timeout_us(config[CONF_TIMEOUT])) + + if CONF_TOTAL in config: + sens = yield sensor.new_sensor(config[CONF_TOTAL]) + cg.add(var.set_total_sensor(sens)) diff --git a/esphome/components/pulse_width/sensor.py b/esphome/components/pulse_width/sensor.py index 8328da2ac084..228c5f8dfe54 100644 --- a/esphome/components/pulse_width/sensor.py +++ b/esphome/components/pulse_width/sensor.py @@ -2,17 +2,26 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor -from esphome.const import CONF_ID, CONF_PIN, UNIT_SECOND, ICON_TIMER +from esphome.const import CONF_ID, CONF_PIN, DEVICE_CLASS_EMPTY, UNIT_SECOND, ICON_TIMER -pulse_width_ns = cg.esphome_ns.namespace('pulse_width') +pulse_width_ns = cg.esphome_ns.namespace("pulse_width") -PulseWidthSensor = pulse_width_ns.class_('PulseWidthSensor', sensor.Sensor, cg.PollingComponent) +PulseWidthSensor = pulse_width_ns.class_( + "PulseWidthSensor", sensor.Sensor, cg.PollingComponent +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_SECOND, ICON_TIMER, 3).extend({ - cv.GenerateID(): cv.declare_id(PulseWidthSensor), - cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema, - pins.validate_has_interrupt), -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_SECOND, ICON_TIMER, 3, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(PulseWidthSensor), + cv.Required(CONF_PIN): cv.All( + pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt + ), + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): diff --git a/esphome/components/pzem004t/sensor.py b/esphome/components/pzem004t/sensor.py index b54ba4887cd7..1228fc4ab667 100644 --- a/esphome/components/pzem004t/sensor.py +++ b/esphome/components/pzem004t/sensor.py @@ -1,23 +1,49 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, uart -from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \ - CONF_ENERGY, UNIT_VOLT, ICON_FLASH, ICON_COUNTER, UNIT_AMPERE, UNIT_WATT, \ - UNIT_WATT_HOURS +from esphome.const import ( + CONF_CURRENT, + CONF_ID, + CONF_POWER, + CONF_VOLTAGE, + CONF_ENERGY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, + UNIT_WATT_HOURS, +) -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] -pzem004t_ns = cg.esphome_ns.namespace('pzem004t') -PZEM004T = pzem004t_ns.class_('PZEM004T', cg.PollingComponent, uart.UARTDevice) +pzem004t_ns = cg.esphome_ns.namespace("pzem004t") +PZEM004T = pzem004t_ns.class_("PZEM004T", cg.PollingComponent, uart.UARTDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(PZEM004T), - - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), - cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), - cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 0), - cv.Optional(CONF_ENERGY): sensor.sensor_schema(UNIT_WATT_HOURS, ICON_COUNTER, 0) -}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PZEM004T), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER + ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + UNIT_WATT_HOURS, ICON_EMPTY, 0, DEVICE_CLASS_ENERGY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): diff --git a/esphome/components/pzemac/sensor.py b/esphome/components/pzemac/sensor.py index e3d2b9074298..aa30549c258b 100644 --- a/esphome/components/pzemac/sensor.py +++ b/esphome/components/pzemac/sensor.py @@ -1,25 +1,62 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, modbus -from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \ - CONF_FREQUENCY, UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, \ - ICON_POWER, CONF_POWER_FACTOR, ICON_CURRENT_AC, UNIT_HERTZ, \ - CONF_ENERGY, UNIT_WATT_HOURS, ICON_COUNTER +from esphome.const import ( + CONF_CURRENT, + CONF_ENERGY, + CONF_ID, + CONF_POWER, + CONF_VOLTAGE, + CONF_FREQUENCY, + CONF_POWER_FACTOR, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_ENERGY, + ICON_EMPTY, + ICON_CURRENT_AC, + UNIT_HERTZ, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, + UNIT_EMPTY, + UNIT_WATT_HOURS, +) -AUTO_LOAD = ['modbus'] +AUTO_LOAD = ["modbus"] -pzemac_ns = cg.esphome_ns.namespace('pzemac') -PZEMAC = pzemac_ns.class_('PZEMAC', cg.PollingComponent, modbus.ModbusDevice) +pzemac_ns = cg.esphome_ns.namespace("pzemac") +PZEMAC = pzemac_ns.class_("PZEMAC", cg.PollingComponent, modbus.ModbusDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(PZEMAC), - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), - cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 3), - cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_POWER, 1), - cv.Optional(CONF_ENERGY): sensor.sensor_schema(UNIT_WATT_HOURS, ICON_COUNTER, 0), - cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1), - cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2), -}).extend(cv.polling_component_schema('60s')).extend(modbus.modbus_device_schema(0x01)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PZEMAC), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 3, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER + ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + UNIT_WATT_HOURS, ICON_EMPTY, 0, DEVICE_CLASS_ENERGY + ), + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_POWER_FACTOR + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(modbus.modbus_device_schema(0x01)) +) def to_code(config): diff --git a/esphome/components/pzemdc/sensor.py b/esphome/components/pzemdc/sensor.py index 8c6fd08868e6..962c97035942 100644 --- a/esphome/components/pzemdc/sensor.py +++ b/esphome/components/pzemdc/sensor.py @@ -1,20 +1,43 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, modbus -from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \ - UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT, ICON_POWER, ICON_CURRENT_AC +from esphome.const import ( + CONF_CURRENT, + CONF_ID, + CONF_POWER, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, +) -AUTO_LOAD = ['modbus'] +AUTO_LOAD = ["modbus"] -pzemdc_ns = cg.esphome_ns.namespace('pzemdc') -PZEMDC = pzemdc_ns.class_('PZEMDC', cg.PollingComponent, modbus.ModbusDevice) +pzemdc_ns = cg.esphome_ns.namespace("pzemdc") +PZEMDC = pzemdc_ns.class_("PZEMDC", cg.PollingComponent, modbus.ModbusDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(PZEMDC), - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), - cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 3), - cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_POWER, 1), -}).extend(cv.polling_component_schema('60s')).extend(modbus.modbus_device_schema(0x01)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PZEMDC), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 3, DEVICE_CLASS_CURRENT + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(modbus.modbus_device_schema(0x01)) +) def to_code(config): diff --git a/esphome/components/qmc5883l/sensor.py b/esphome/components/qmc5883l/sensor.py index 8a2952f54f10..43594276289e 100644 --- a/esphome/components/qmc5883l/sensor.py +++ b/esphome/components/qmc5883l/sensor.py @@ -1,23 +1,33 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import (CONF_ADDRESS, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, ICON_MAGNET, - UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION, - CONF_UPDATE_INTERVAL) - -DEPENDENCIES = ['i2c'] - -qmc5883l_ns = cg.esphome_ns.namespace('qmc5883l') - -CONF_FIELD_STRENGTH_X = 'field_strength_x' -CONF_FIELD_STRENGTH_Y = 'field_strength_y' -CONF_FIELD_STRENGTH_Z = 'field_strength_z' -CONF_HEADING = 'heading' +from esphome.const import ( + CONF_ADDRESS, + CONF_ID, + CONF_OVERSAMPLING, + CONF_RANGE, + DEVICE_CLASS_EMPTY, + ICON_MAGNET, + UNIT_MICROTESLA, + UNIT_DEGREES, + ICON_SCREEN_ROTATION, + CONF_UPDATE_INTERVAL, +) + +DEPENDENCIES = ["i2c"] + +qmc5883l_ns = cg.esphome_ns.namespace("qmc5883l") + +CONF_FIELD_STRENGTH_X = "field_strength_x" +CONF_FIELD_STRENGTH_Y = "field_strength_y" +CONF_FIELD_STRENGTH_Z = "field_strength_z" +CONF_HEADING = "heading" QMC5883LComponent = qmc5883l_ns.class_( - 'QMC5883LComponent', cg.PollingComponent, i2c.I2CDevice) + "QMC5883LComponent", cg.PollingComponent, i2c.I2CDevice +) -QMC5883LDatarate = qmc5883l_ns.enum('QMC5883LDatarate') +QMC5883LDatarate = qmc5883l_ns.enum("QMC5883LDatarate") QMC5883LDatarates = { 10: QMC5883LDatarate.QMC5883L_DATARATE_10_HZ, 50: QMC5883LDatarate.QMC5883L_DATARATE_50_HZ, @@ -25,13 +35,13 @@ 200: QMC5883LDatarate.QMC5883L_DATARATE_200_HZ, } -QMC5883LRange = qmc5883l_ns.enum('QMC5883LRange') +QMC5883LRange = qmc5883l_ns.enum("QMC5883LRange") QMC5883L_RANGES = { 200: QMC5883LRange.QMC5883L_RANGE_200_UT, 800: QMC5883LRange.QMC5883L_RANGE_800_UT, } -QMC5883LOversampling = qmc5883l_ns.enum('QMC5883LOversampling') +QMC5883LOversampling = qmc5883l_ns.enum("QMC5883LOversampling") QMC5883LOversamplings = { 512: QMC5883LOversampling.QMC5883L_SAMPLING_512, 256: QMC5883LOversampling.QMC5883L_SAMPLING_256, @@ -51,30 +61,45 @@ def validate_enum_bound(value): value = cv.string(value) for unit in _units: if value.endswith(unit): - value = value[:-len(unit)] + value = value[: -len(unit)] break return enum_bound(value) - return validate_enum_bound + return validate_enum_bound -field_strength_schema = sensor.sensor_schema(UNIT_MICROTESLA, ICON_MAGNET, 1) -heading_schema = sensor.sensor_schema(UNIT_DEGREES, ICON_SCREEN_ROTATION, 1) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(QMC5883LComponent), - cv.Optional(CONF_ADDRESS): cv.i2c_address, - cv.Optional(CONF_RANGE, default='200µT'): validate_enum(QMC5883L_RANGES, units=["uT", "µT"]), - cv.Optional(CONF_OVERSAMPLING, default="512x"): validate_enum(QMC5883LOversamplings, units="x"), - cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema, - cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, - cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, - cv.Optional(CONF_HEADING): heading_schema, -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x0D)) +field_strength_schema = sensor.sensor_schema( + UNIT_MICROTESLA, ICON_MAGNET, 1, DEVICE_CLASS_EMPTY +) +heading_schema = sensor.sensor_schema( + UNIT_DEGREES, ICON_SCREEN_ROTATION, 1, DEVICE_CLASS_EMPTY +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(QMC5883LComponent), + cv.Optional(CONF_ADDRESS): cv.i2c_address, + cv.Optional(CONF_RANGE, default="200µT"): validate_enum( + QMC5883L_RANGES, units=["uT", "µT"] + ), + cv.Optional(CONF_OVERSAMPLING, default="512x"): validate_enum( + QMC5883LOversamplings, units="x" + ), + cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema, + cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, + cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, + cv.Optional(CONF_HEADING): heading_schema, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x0D)) +) def auto_data_rate(config): interval_sec = config[CONF_UPDATE_INTERVAL].seconds - interval_hz = 1.0/interval_sec + interval_hz = 1.0 / interval_sec for datarate in sorted(QMC5883LDatarates.keys()): if float(datarate) >= interval_hz: return QMC5883LDatarates[datarate] diff --git a/esphome/components/rc522/__init__.py b/esphome/components/rc522/__init__.py index 7b4df37ce213..970b867e793f 100644 --- a/esphome/components/rc522/__init__.py +++ b/esphome/components/rc522/__init__.py @@ -5,23 +5,29 @@ from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN from esphome.core import coroutine -CODEOWNERS = ['@glmnet'] -AUTO_LOAD = ['binary_sensor'] +CODEOWNERS = ["@glmnet"] +AUTO_LOAD = ["binary_sensor"] MULTI_CONF = True -CONF_RC522_ID = 'rc522_id' +CONF_RC522_ID = "rc522_id" -rc522_ns = cg.esphome_ns.namespace('rc522') -RC522 = rc522_ns.class_('RC522', cg.PollingComponent, i2c.I2CDevice) -RC522Trigger = rc522_ns.class_('RC522Trigger', automation.Trigger.template(cg.std_string)) +rc522_ns = cg.esphome_ns.namespace("rc522") +RC522 = rc522_ns.class_("RC522", cg.PollingComponent, i2c.I2CDevice) +RC522Trigger = rc522_ns.class_( + "RC522Trigger", automation.Trigger.template(cg.std_string) +) -RC522_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(RC522), - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_ON_TAG): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), - }), -}).extend(cv.polling_component_schema('1s')) +RC522_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(RC522), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_ON_TAG): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), + } + ), + } +).extend(cv.polling_component_schema("1s")) @coroutine @@ -35,4 +41,4 @@ def setup_rc522(var, config): for conf in config.get(CONF_ON_TAG, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) cg.add(var.register_trigger(trigger)) - yield automation.build_automation(trigger, [(cg.std_string, 'x')], conf) + yield automation.build_automation(trigger, [(cg.std_string, "x")], conf) diff --git a/esphome/components/rc522/binary_sensor.py b/esphome/components/rc522/binary_sensor.py index 675db2f1304c..89eef2f97612 100644 --- a/esphome/components/rc522/binary_sensor.py +++ b/esphome/components/rc522/binary_sensor.py @@ -5,31 +5,39 @@ from esphome.core import HexInt, coroutine from . import rc522_ns, RC522, CONF_RC522_ID -DEPENDENCIES = ['rc522'] +DEPENDENCIES = ["rc522"] def validate_uid(value): value = cv.string_strict(value) - for x in value.split('-'): + for x in value.split("-"): if len(x) != 2: - raise cv.Invalid("Each part (separated by '-') of the UID must be two characters " - "long.") + raise cv.Invalid( + "Each part (separated by '-') of the UID must be two characters " + "long." + ) try: x = int(x, 16) except ValueError as err: - raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.") from err + raise cv.Invalid( + "Valid characters for parts of a UID are 0123456789ABCDEF." + ) from err if x < 0 or x > 255: - raise cv.Invalid("Valid values for UID parts (separated by '-') are 00 to FF") + raise cv.Invalid( + "Valid values for UID parts (separated by '-') are 00 to FF" + ) return value -RC522BinarySensor = rc522_ns.class_('RC522BinarySensor', binary_sensor.BinarySensor) +RC522BinarySensor = rc522_ns.class_("RC522BinarySensor", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(RC522BinarySensor), - cv.GenerateID(CONF_RC522_ID): cv.use_id(RC522), - cv.Required(CONF_UID): validate_uid, -}) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(RC522BinarySensor), + cv.GenerateID(CONF_RC522_ID): cv.use_id(RC522), + cv.Required(CONF_UID): validate_uid, + } +) @coroutine @@ -39,5 +47,5 @@ def to_code(config): hub = yield cg.get_variable(config[CONF_RC522_ID]) cg.add(hub.register_tag(var)) - addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')] + addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")] cg.add(var.set_uid(addr)) diff --git a/esphome/components/rc522_i2c/__init__.py b/esphome/components/rc522_i2c/__init__.py index c5bb72ee531d..532adfce79ed 100644 --- a/esphome/components/rc522_i2c/__init__.py +++ b/esphome/components/rc522_i2c/__init__.py @@ -3,17 +3,21 @@ from esphome.components import i2c, rc522 from esphome.const import CONF_ID -CODEOWNERS = ['@glmnet'] -DEPENDENCIES = ['i2c'] -AUTO_LOAD = ['rc522'] +CODEOWNERS = ["@glmnet"] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["rc522"] -rc522_i2c_ns = cg.esphome_ns.namespace('rc522_i2c') -RC522I2C = rc522_i2c_ns.class_('RC522I2C', rc522.RC522, i2c.I2CDevice) +rc522_i2c_ns = cg.esphome_ns.namespace("rc522_i2c") +RC522I2C = rc522_i2c_ns.class_("RC522I2C", rc522.RC522, i2c.I2CDevice) -CONFIG_SCHEMA = cv.All(rc522.RC522_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(RC522I2C), -}).extend(i2c.i2c_device_schema(0x2c))) +CONFIG_SCHEMA = cv.All( + rc522.RC522_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(RC522I2C), + } + ).extend(i2c.i2c_device_schema(0x2C)) +) def to_code(config): diff --git a/esphome/components/rc522_spi/__init__.py b/esphome/components/rc522_spi/__init__.py index 37f78b66d091..6ae163bcb45c 100644 --- a/esphome/components/rc522_spi/__init__.py +++ b/esphome/components/rc522_spi/__init__.py @@ -3,16 +3,20 @@ from esphome.components import spi, rc522 from esphome.const import CONF_ID -CODEOWNERS = ['@glmnet'] -DEPENDENCIES = ['spi'] -AUTO_LOAD = ['rc522'] +CODEOWNERS = ["@glmnet"] +DEPENDENCIES = ["spi"] +AUTO_LOAD = ["rc522"] -rc522_spi_ns = cg.esphome_ns.namespace('rc522_spi') -RC522Spi = rc522_spi_ns.class_('RC522Spi', rc522.RC522, spi.SPIDevice) +rc522_spi_ns = cg.esphome_ns.namespace("rc522_spi") +RC522Spi = rc522_spi_ns.class_("RC522Spi", rc522.RC522, spi.SPIDevice) -CONFIG_SCHEMA = cv.All(rc522.RC522_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(RC522Spi), -}).extend(spi.spi_device_schema(cs_pin_required=True))) +CONFIG_SCHEMA = cv.All( + rc522.RC522_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(RC522Spi), + } + ).extend(spi.spi_device_schema(cs_pin_required=True)) +) def to_code(config): diff --git a/esphome/components/rc522_spi/binary_sensor.py b/esphome/components/rc522_spi/binary_sensor.py index 1f0eafe6af80..f5400a2331c1 100644 --- a/esphome/components/rc522_spi/binary_sensor.py +++ b/esphome/components/rc522_spi/binary_sensor.py @@ -1,6 +1,6 @@ import esphome.components.rc522.binary_sensor as rc522_binary_sensor -DEPENDENCIES = ['rc522'] +DEPENDENCIES = ["rc522"] CONFIG_SCHEMA = rc522_binary_sensor.CONFIG_SCHEMA diff --git a/esphome/components/rdm6300/__init__.py b/esphome/components/rdm6300/__init__.py index ee5077c31576..a416d95a122a 100644 --- a/esphome/components/rdm6300/__init__.py +++ b/esphome/components/rdm6300/__init__.py @@ -4,19 +4,29 @@ from esphome.components import uart from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID -DEPENDENCIES = ['uart'] -AUTO_LOAD = ['binary_sensor'] +DEPENDENCIES = ["uart"] +AUTO_LOAD = ["binary_sensor"] -rdm6300_ns = cg.esphome_ns.namespace('rdm6300') -RDM6300Component = rdm6300_ns.class_('RDM6300Component', cg.Component, uart.UARTDevice) -RDM6300Trigger = rdm6300_ns.class_('RDM6300Trigger', automation.Trigger.template(cg.uint32)) +rdm6300_ns = cg.esphome_ns.namespace("rdm6300") +RDM6300Component = rdm6300_ns.class_("RDM6300Component", cg.Component, uart.UARTDevice) +RDM6300Trigger = rdm6300_ns.class_( + "RDM6300Trigger", automation.Trigger.template(cg.uint32) +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(RDM6300Component), - cv.Optional(CONF_ON_TAG): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RDM6300Trigger), - }), -}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RDM6300Component), + cv.Optional(CONF_ON_TAG): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RDM6300Trigger), + } + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): @@ -27,4 +37,4 @@ def to_code(config): for conf in config.get(CONF_ON_TAG, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) cg.add(var.register_trigger(trigger)) - yield automation.build_automation(trigger, [(cg.uint32, 'x')], conf) + yield automation.build_automation(trigger, [(cg.uint32, "x")], conf) diff --git a/esphome/components/rdm6300/binary_sensor.py b/esphome/components/rdm6300/binary_sensor.py index 81b24bed0ebe..02e0b6ceb600 100644 --- a/esphome/components/rdm6300/binary_sensor.py +++ b/esphome/components/rdm6300/binary_sensor.py @@ -4,16 +4,20 @@ from esphome.const import CONF_UID, CONF_ID from . import rdm6300_ns -DEPENDENCIES = ['rdm6300'] +DEPENDENCIES = ["rdm6300"] -CONF_RDM6300_ID = 'rdm6300_id' -RDM6300BinarySensor = rdm6300_ns.class_('RDM6300BinarySensor', binary_sensor.BinarySensor) +CONF_RDM6300_ID = "rdm6300_id" +RDM6300BinarySensor = rdm6300_ns.class_( + "RDM6300BinarySensor", binary_sensor.BinarySensor +) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(RDM6300BinarySensor), - cv.GenerateID(CONF_RDM6300_ID): cv.use_id(rdm6300.RDM6300Component), - cv.Required(CONF_UID): cv.uint32_t, -}) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(RDM6300BinarySensor), + cv.GenerateID(CONF_RDM6300_ID): cv.use_id(rdm6300.RDM6300Component), + cv.Required(CONF_UID): cv.uint32_t, + } +) def to_code(config): diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 770394faec69..96579c05bbac 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -2,29 +2,56 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import binary_sensor -from esphome.const import CONF_DATA, CONF_TRIGGER_ID, CONF_NBITS, CONF_ADDRESS, \ - CONF_COMMAND, CONF_CODE, CONF_PULSE_LENGTH, CONF_SYNC, CONF_ZERO, CONF_ONE, CONF_INVERTED, \ - CONF_PROTOCOL, CONF_GROUP, CONF_DEVICE, CONF_STATE, CONF_CHANNEL, CONF_FAMILY, CONF_REPEAT, \ - CONF_WAIT_TIME, CONF_TIMES, CONF_TYPE_ID, CONF_CARRIER_FREQUENCY, CONF_RC_CODE_1, CONF_RC_CODE_2 +from esphome.const import ( + CONF_DATA, + CONF_TRIGGER_ID, + CONF_NBITS, + CONF_ADDRESS, + CONF_COMMAND, + CONF_CODE, + CONF_PULSE_LENGTH, + CONF_SYNC, + CONF_ZERO, + CONF_ONE, + CONF_INVERTED, + CONF_PROTOCOL, + CONF_GROUP, + CONF_DEVICE, + CONF_STATE, + CONF_CHANNEL, + CONF_FAMILY, + CONF_REPEAT, + CONF_WAIT_TIME, + CONF_TIMES, + CONF_TYPE_ID, + CONF_CARRIER_FREQUENCY, + CONF_RC_CODE_1, + CONF_RC_CODE_2, +) from esphome.core import coroutine +from esphome.jsonschema import jschema_extractor from esphome.util import Registry, SimpleRegistry -AUTO_LOAD = ['binary_sensor'] +AUTO_LOAD = ["binary_sensor"] -CONF_RECEIVER_ID = 'receiver_id' -CONF_TRANSMITTER_ID = 'transmitter_id' +CONF_RECEIVER_ID = "receiver_id" +CONF_TRANSMITTER_ID = "transmitter_id" -ns = remote_base_ns = cg.esphome_ns.namespace('remote_base') -RemoteProtocol = ns.class_('RemoteProtocol') -RemoteReceiverListener = ns.class_('RemoteReceiverListener') -RemoteReceiverBinarySensorBase = ns.class_('RemoteReceiverBinarySensorBase', - binary_sensor.BinarySensor, cg.Component) -RemoteReceiverTrigger = ns.class_('RemoteReceiverTrigger', automation.Trigger, - RemoteReceiverListener) -RemoteTransmitterDumper = ns.class_('RemoteTransmitterDumper') -RemoteTransmitterActionBase = ns.class_('RemoteTransmitterActionBase', automation.Action) -RemoteReceiverBase = ns.class_('RemoteReceiverBase') -RemoteTransmitterBase = ns.class_('RemoteTransmitterBase') +ns = remote_base_ns = cg.esphome_ns.namespace("remote_base") +RemoteProtocol = ns.class_("RemoteProtocol") +RemoteReceiverListener = ns.class_("RemoteReceiverListener") +RemoteReceiverBinarySensorBase = ns.class_( + "RemoteReceiverBinarySensorBase", binary_sensor.BinarySensor, cg.Component +) +RemoteReceiverTrigger = ns.class_( + "RemoteReceiverTrigger", automation.Trigger, RemoteReceiverListener +) +RemoteTransmitterDumper = ns.class_("RemoteTransmitterDumper") +RemoteTransmitterActionBase = ns.class_( + "RemoteTransmitterActionBase", automation.Action +) +RemoteReceiverBase = ns.class_("RemoteReceiverBase") +RemoteTransmitterBase = ns.class_("RemoteTransmitterBase") def templatize(value): @@ -47,11 +74,13 @@ def register_binary_sensor(name, type, schema): def register_trigger(name, type, data_type): - validator = automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(type), - cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), - }) - registerer = TRIGGER_REGISTRY.register(f'on_{name}', validator) + validator = automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(type), + cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), + } + ) + registerer = TRIGGER_REGISTRY.register(f"on_{name}", validator) def decorator(func): @coroutine @@ -59,7 +88,7 @@ def new_func(config): var = cg.new_Pvariable(config[CONF_TRIGGER_ID]) yield register_listener(var, config) yield coroutine(func)(var, config) - yield automation.build_automation(var, [(data_type, 'x')], config) + yield automation.build_automation(var, [(data_type, "x")], config) yield var return registerer(new_func) @@ -84,21 +113,30 @@ def new_func(config, dumper_id): def validate_repeat(value): if isinstance(value, dict): - return cv.Schema({ - cv.Required(CONF_TIMES): cv.templatable(cv.positive_int), - cv.Optional(CONF_WAIT_TIME, default='25ms'): - cv.templatable(cv.positive_time_period_microseconds), - })(value) + return cv.Schema( + { + cv.Required(CONF_TIMES): cv.templatable(cv.positive_int), + cv.Optional(CONF_WAIT_TIME, default="25ms"): cv.templatable( + cv.positive_time_period_microseconds + ), + } + )(value) return validate_repeat({CONF_TIMES: value}) -def register_action(name, type_, schema): - validator = templatize(schema).extend({ +BASE_REMOTE_TRANSMITTER_SCHEMA = cv.Schema( + { cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase), cv.Optional(CONF_REPEAT): validate_repeat, - }) - registerer = automation.register_action(f'remote_transmitter.transmit_{name}', - type_, validator) + } +) + + +def register_action(name, type_, schema): + validator = templatize(schema).extend(BASE_REMOTE_TRANSMITTER_SCHEMA) + registerer = automation.register_action( + f"remote_transmitter.transmit_{name}", type_, validator + ) def decorator(func): @coroutine @@ -121,38 +159,50 @@ def new_func(config, action_id, template_arg, args): def declare_protocol(name): - data = ns.struct(f'{name}Data') - binary_sensor_ = ns.class_(f'{name}BinarySensor', RemoteReceiverBinarySensorBase) - trigger = ns.class_(f'{name}Trigger', RemoteReceiverTrigger) - action = ns.class_(f'{name}Action', RemoteTransmitterActionBase) - dumper = ns.class_(f'{name}Dumper', RemoteTransmitterDumper) + data = ns.struct(f"{name}Data") + binary_sensor_ = ns.class_(f"{name}BinarySensor", RemoteReceiverBinarySensorBase) + trigger = ns.class_(f"{name}Trigger", RemoteReceiverTrigger) + action = ns.class_(f"{name}Action", RemoteTransmitterActionBase) + dumper = ns.class_(f"{name}Dumper", RemoteTransmitterDumper) return data, binary_sensor_, trigger, action, dumper -BINARY_SENSOR_REGISTRY = Registry(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), -})) -validate_binary_sensor = cv.validate_registry_entry('remote receiver', BINARY_SENSOR_REGISTRY) +BINARY_SENSOR_REGISTRY = Registry( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), + } + ) +) +validate_binary_sensor = cv.validate_registry_entry( + "remote receiver", BINARY_SENSOR_REGISTRY +) TRIGGER_REGISTRY = SimpleRegistry() -DUMPER_REGISTRY = Registry({ - cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), -}) +DUMPER_REGISTRY = Registry( + { + cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), + } +) def validate_dumpers(value): - if isinstance(value, str) and value.lower() == 'all': + if isinstance(value, str) and value.lower() == "all": return validate_dumpers(list(DUMPER_REGISTRY.keys())) - return cv.validate_registry('dumper', DUMPER_REGISTRY)(value) + return cv.validate_registry("dumper", DUMPER_REGISTRY)(value) def validate_triggers(base_schema): assert isinstance(base_schema, cv.Schema) + @jschema_extractor("triggers") def validator(config): added_keys = {} for key, (_, valid) in TRIGGER_REGISTRY.items(): added_keys[cv.Optional(key)] = valid new_schema = base_schema.extend(added_keys) + # pylint: disable=comparison-with-callable + if config == jschema_extractor: + return new_schema return new_schema(config) return validator @@ -160,7 +210,9 @@ def validator(config): @coroutine def build_binary_sensor(full_config): - registry_entry, config = cg.extract_registry_entry_config(BINARY_SENSOR_REGISTRY, full_config) + registry_entry, config = cg.extract_registry_entry_config( + BINARY_SENSOR_REGISTRY, full_config + ) type_id = full_config[CONF_TYPE_ID] builder = registry_entry.coroutine_fun var = cg.new_Pvariable(type_id) @@ -190,62 +242,72 @@ def build_dumpers(config): # JVC -JVCData, JVCBinarySensor, JVCTrigger, JVCAction, JVCDumper = declare_protocol('JVC') +JVCData, JVCBinarySensor, JVCTrigger, JVCAction, JVCDumper = declare_protocol("JVC") JVC_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t}) -@register_binary_sensor('jvc', JVCBinarySensor, JVC_SCHEMA) +@register_binary_sensor("jvc", JVCBinarySensor, JVC_SCHEMA) def jvc_binary_sensor(var, config): - cg.add(var.set_data(cg.StructInitializer( - JVCData, - ('data', config[CONF_DATA]), - ))) + cg.add( + var.set_data( + cg.StructInitializer( + JVCData, + ("data", config[CONF_DATA]), + ) + ) + ) -@register_trigger('jvc', JVCTrigger, JVCData) +@register_trigger("jvc", JVCTrigger, JVCData) def jvc_trigger(var, config): pass -@register_dumper('jvc', JVCDumper) +@register_dumper("jvc", JVCDumper) def jvc_dumper(var, config): pass -@register_action('jvc', JVCAction, JVC_SCHEMA) +@register_action("jvc", JVCAction, JVC_SCHEMA) def jvc_action(var, config, args): template_ = yield cg.templatable(config[CONF_DATA], args, cg.uint32) cg.add(var.set_data(template_)) # LG -LGData, LGBinarySensor, LGTrigger, LGAction, LGDumper = declare_protocol('LG') -LG_SCHEMA = cv.Schema({ - cv.Required(CONF_DATA): cv.hex_uint32_t, - cv.Optional(CONF_NBITS, default=28): cv.one_of(28, 32, int=True), -}) +LGData, LGBinarySensor, LGTrigger, LGAction, LGDumper = declare_protocol("LG") +LG_SCHEMA = cv.Schema( + { + cv.Required(CONF_DATA): cv.hex_uint32_t, + cv.Optional(CONF_NBITS, default=28): cv.one_of(28, 32, int=True), + } +) -@register_binary_sensor('lg', LGBinarySensor, LG_SCHEMA) +@register_binary_sensor("lg", LGBinarySensor, LG_SCHEMA) def lg_binary_sensor(var, config): - cg.add(var.set_data(cg.StructInitializer( - LGData, - ('data', config[CONF_DATA]), - ('nbits', config[CONF_NBITS]), - ))) - - -@register_trigger('lg', LGTrigger, LGData) + cg.add( + var.set_data( + cg.StructInitializer( + LGData, + ("data", config[CONF_DATA]), + ("nbits", config[CONF_NBITS]), + ) + ) + ) + + +@register_trigger("lg", LGTrigger, LGData) def lg_trigger(var, config): pass -@register_dumper('lg', LGDumper) +@register_dumper("lg", LGDumper) def lg_dumper(var, config): pass -@register_action('lg', LGAction, LG_SCHEMA) +@register_action("lg", LGAction, LG_SCHEMA) def lg_action(var, config, args): template_ = yield cg.templatable(config[CONF_DATA], args, cg.uint32) cg.add(var.set_data(template_)) @@ -254,33 +316,39 @@ def lg_action(var, config, args): # NEC -NECData, NECBinarySensor, NECTrigger, NECAction, NECDumper = declare_protocol('NEC') -NEC_SCHEMA = cv.Schema({ - cv.Required(CONF_ADDRESS): cv.hex_uint16_t, - cv.Required(CONF_COMMAND): cv.hex_uint16_t, -}) +NECData, NECBinarySensor, NECTrigger, NECAction, NECDumper = declare_protocol("NEC") +NEC_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.hex_uint16_t, + cv.Required(CONF_COMMAND): cv.hex_uint16_t, + } +) -@register_binary_sensor('nec', NECBinarySensor, NEC_SCHEMA) +@register_binary_sensor("nec", NECBinarySensor, NEC_SCHEMA) def nec_binary_sensor(var, config): - cg.add(var.set_data(cg.StructInitializer( - NECData, - ('address', config[CONF_ADDRESS]), - ('command', config[CONF_COMMAND]), - ))) - - -@register_trigger('nec', NECTrigger, NECData) + cg.add( + var.set_data( + cg.StructInitializer( + NECData, + ("address", config[CONF_ADDRESS]), + ("command", config[CONF_COMMAND]), + ) + ) + ) + + +@register_trigger("nec", NECTrigger, NECData) def nec_trigger(var, config): pass -@register_dumper('nec', NECDumper) +@register_dumper("nec", NECDumper) def nec_dumper(var, config): pass -@register_action('nec', NECAction, NEC_SCHEMA) +@register_action("nec", NECAction, NEC_SCHEMA) def nec_action(var, config, args): template_ = yield cg.templatable(config[CONF_ADDRESS], args, cg.uint16) cg.add(var.set_address(template_)) @@ -289,34 +357,45 @@ def nec_action(var, config, args): # Pioneer -(PioneerData, PioneerBinarySensor, PioneerTrigger, PioneerAction, - PioneerDumper) = declare_protocol('Pioneer') -PIONEER_SCHEMA = cv.Schema({ - cv.Required(CONF_RC_CODE_1): cv.hex_uint16_t, - cv.Optional(CONF_RC_CODE_2, default=0): cv.hex_uint16_t, -}) +( + PioneerData, + PioneerBinarySensor, + PioneerTrigger, + PioneerAction, + PioneerDumper, +) = declare_protocol("Pioneer") +PIONEER_SCHEMA = cv.Schema( + { + cv.Required(CONF_RC_CODE_1): cv.hex_uint16_t, + cv.Optional(CONF_RC_CODE_2, default=0): cv.hex_uint16_t, + } +) -@register_binary_sensor('pioneer', PioneerBinarySensor, PIONEER_SCHEMA) +@register_binary_sensor("pioneer", PioneerBinarySensor, PIONEER_SCHEMA) def pioneer_binary_sensor(var, config): - cg.add(var.set_data(cg.StructInitializer( - PioneerData, - ('rc_code_1', config[CONF_RC_CODE_1]), - ('rc_code_2', config[CONF_RC_CODE_2]), - ))) - - -@register_trigger('pioneer', PioneerTrigger, PioneerData) + cg.add( + var.set_data( + cg.StructInitializer( + PioneerData, + ("rc_code_1", config[CONF_RC_CODE_1]), + ("rc_code_2", config[CONF_RC_CODE_2]), + ) + ) + ) + + +@register_trigger("pioneer", PioneerTrigger, PioneerData) def pioneer_trigger(var, config): pass -@register_dumper('pioneer', PioneerDumper) +@register_dumper("pioneer", PioneerDumper) def pioneer_dumper(var, config): pass -@register_action('pioneer', PioneerAction, PIONEER_SCHEMA) +@register_action("pioneer", PioneerAction, PIONEER_SCHEMA) def pioneer_action(var, config, args): template_ = yield cg.templatable(config[CONF_RC_CODE_1], args, cg.uint16) cg.add(var.set_rc_code_1(template_)) @@ -325,33 +404,41 @@ def pioneer_action(var, config, args): # Sony -SonyData, SonyBinarySensor, SonyTrigger, SonyAction, SonyDumper = declare_protocol('Sony') -SONY_SCHEMA = cv.Schema({ - cv.Required(CONF_DATA): cv.hex_uint32_t, - cv.Optional(CONF_NBITS, default=12): cv.one_of(12, 15, 20, int=True), -}) +SonyData, SonyBinarySensor, SonyTrigger, SonyAction, SonyDumper = declare_protocol( + "Sony" +) +SONY_SCHEMA = cv.Schema( + { + cv.Required(CONF_DATA): cv.hex_uint32_t, + cv.Optional(CONF_NBITS, default=12): cv.one_of(12, 15, 20, int=True), + } +) -@register_binary_sensor('sony', SonyBinarySensor, SONY_SCHEMA) +@register_binary_sensor("sony", SonyBinarySensor, SONY_SCHEMA) def sony_binary_sensor(var, config): - cg.add(var.set_data(cg.StructInitializer( - SonyData, - ('data', config[CONF_DATA]), - ('nbits', config[CONF_NBITS]), - ))) - - -@register_trigger('sony', SonyTrigger, SonyData) + cg.add( + var.set_data( + cg.StructInitializer( + SonyData, + ("data", config[CONF_DATA]), + ("nbits", config[CONF_NBITS]), + ) + ) + ) + + +@register_trigger("sony", SonyTrigger, SonyData) def sony_trigger(var, config): pass -@register_dumper('sony', SonyDumper) +@register_dumper("sony", SonyDumper) def sony_dumper(var, config): pass -@register_action('sony', SonyAction, SONY_SCHEMA) +@register_action("sony", SonyAction, SONY_SCHEMA) def sony_action(var, config, args): template_ = yield cg.templatable(config[CONF_DATA], args, cg.uint16) cg.add(var.set_data(template_)) @@ -367,22 +454,30 @@ def validate_raw_alternating(value): this_negative = val < 0 if i != 0: if this_negative == last_negative: - raise cv.Invalid("Values must alternate between being positive and negative, " - "please see index {} and {}".format(i, i + 1), [i]) + raise cv.Invalid( + "Values must alternate between being positive and negative, " + "please see index {} and {}".format(i, i + 1), + [i], + ) last_negative = this_negative return value -RawData, RawBinarySensor, RawTrigger, RawAction, RawDumper = declare_protocol('Raw') -CONF_CODE_STORAGE_ID = 'code_storage_id' -RAW_SCHEMA = cv.Schema({ - cv.Required(CONF_CODE): cv.All([cv.Any(cv.int_, cv.time_period_microseconds)], - cv.Length(min=1), validate_raw_alternating), - cv.GenerateID(CONF_CODE_STORAGE_ID): cv.declare_id(cg.int32), -}) +RawData, RawBinarySensor, RawTrigger, RawAction, RawDumper = declare_protocol("Raw") +CONF_CODE_STORAGE_ID = "code_storage_id" +RAW_SCHEMA = cv.Schema( + { + cv.Required(CONF_CODE): cv.All( + [cv.Any(cv.int_, cv.time_period_microseconds)], + cv.Length(min=1), + validate_raw_alternating, + ), + cv.GenerateID(CONF_CODE_STORAGE_ID): cv.declare_id(cg.int32), + } +) -@register_binary_sensor('raw', RawBinarySensor, RAW_SCHEMA) +@register_binary_sensor("raw", RawBinarySensor, RAW_SCHEMA) def raw_binary_sensor(var, config): code_ = config[CONF_CODE] arr = cg.progmem_array(config[CONF_CODE_STORAGE_ID], code_) @@ -390,19 +485,27 @@ def raw_binary_sensor(var, config): cg.add(var.set_len(len(code_))) -@register_trigger('raw', RawTrigger, cg.std_vector.template(cg.int32)) +@register_trigger("raw", RawTrigger, cg.std_vector.template(cg.int32)) def raw_trigger(var, config): pass -@register_dumper('raw', RawDumper) +@register_dumper("raw", RawDumper) def raw_dumper(var, config): pass -@register_action('raw', RawAction, RAW_SCHEMA.extend({ - cv.Optional(CONF_CARRIER_FREQUENCY, default='0Hz'): cv.All(cv.frequency, cv.int_), -})) +@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_): @@ -417,33 +520,39 @@ def raw_action(var, config, args): # RC5 -RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol('RC5') -RC5_SCHEMA = cv.Schema({ - cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)), - cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)), -}) +RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5") +RC5_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)), + cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)), + } +) -@register_binary_sensor('rc5', RC5BinarySensor, RC5_SCHEMA) +@register_binary_sensor("rc5", RC5BinarySensor, RC5_SCHEMA) def rc5_binary_sensor(var, config): - cg.add(var.set_data(cg.StructInitializer( - RC5Data, - ('address', config[CONF_ADDRESS]), - ('command', config[CONF_COMMAND]), - ))) - - -@register_trigger('rc5', RC5Trigger, RC5Data) + cg.add( + var.set_data( + cg.StructInitializer( + RC5Data, + ("address", config[CONF_ADDRESS]), + ("command", config[CONF_COMMAND]), + ) + ) + ) + + +@register_trigger("rc5", RC5Trigger, RC5Data) def rc5_trigger(var, config): pass -@register_dumper('rc5', RC5Dumper) +@register_dumper("rc5", RC5Dumper) def rc5_dumper(var, config): pass -@register_action('rc5', RC5Action, RC5_SCHEMA) +@register_action("rc5", RC5Action, RC5_SCHEMA) def rc5_action(var, config, args): template_ = yield cg.templatable(config[CONF_ADDRESS], args, cg.uint8) cg.add(var.set_address(template_)) @@ -456,13 +565,15 @@ def rc5_action(var, config, args): RC_SWITCH_PROTOCOL_SCHEMA = cv.Any( cv.int_range(min=1, max=8), - cv.Schema({ - cv.Required(CONF_PULSE_LENGTH): cv.uint32_t, - cv.Optional(CONF_SYNC, default=[1, 31]): RC_SWITCH_TIMING_SCHEMA, - cv.Optional(CONF_ZERO, default=[1, 3]): RC_SWITCH_TIMING_SCHEMA, - cv.Optional(CONF_ONE, default=[3, 1]): RC_SWITCH_TIMING_SCHEMA, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - }) + cv.Schema( + { + cv.Required(CONF_PULSE_LENGTH): cv.uint32_t, + cv.Optional(CONF_SYNC, default=[1, 31]): RC_SWITCH_TIMING_SCHEMA, + cv.Optional(CONF_ZERO, default=[1, 3]): RC_SWITCH_TIMING_SCHEMA, + cv.Optional(CONF_ONE, default=[3, 1]): RC_SWITCH_TIMING_SCHEMA, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } + ), ) @@ -470,12 +581,16 @@ def validate_rc_switch_code(value): if not isinstance(value, (str, str)): raise cv.Invalid("All RCSwitch codes must be in quotes ('')") for c in value: - if c not in ('0', '1'): - raise cv.Invalid("Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed" - "".format(c)) + if c not in ("0", "1"): + raise cv.Invalid( + "Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed" + "".format(c) + ) if len(value) > 64: - raise cv.Invalid("Maximum length for RCSwitch codes is 64, code '{}' has length {}" - "".format(value, len(value))) + raise cv.Invalid( + "Maximum length for RCSwitch codes is 64, code '{}' has length {}" + "".format(value, len(value)) + ) if not value: raise cv.Invalid("RCSwitch code must not be empty") return value @@ -485,13 +600,17 @@ def validate_rc_switch_raw_code(value): if not isinstance(value, (str, str)): raise cv.Invalid("All RCSwitch raw codes must be in quotes ('')") for c in value: - if c not in ('0', '1', 'x'): + if c not in ("0", "1", "x"): raise cv.Invalid( - "Invalid RCSwitch raw code character '{}'.Only '0', '1' and 'x' are allowed" - .format(c)) + "Invalid RCSwitch raw code character '{}'.Only '0', '1' and 'x' are allowed".format( + c + ) + ) if len(value) > 64: - raise cv.Invalid("Maximum length for RCSwitch raw codes is 64, code '{}' has length {}" - "".format(value, len(value))) + raise cv.Invalid( + "Maximum length for RCSwitch raw codes is 64, code '{}' has length {}" + "".format(value, len(value)) + ) if not value: raise cv.Invalid("RCSwitch raw code must not be empty") return value @@ -501,220 +620,379 @@ def build_rc_switch_protocol(config): if isinstance(config, int): return rc_switch_protocols[config] pl = config[CONF_PULSE_LENGTH] - return RCSwitchBase(config[CONF_SYNC][0] * pl, config[CONF_SYNC][1] * pl, - config[CONF_ZERO][0] * pl, config[CONF_ZERO][1] * pl, - config[CONF_ONE][0] * pl, config[CONF_ONE][1] * pl, - config[CONF_INVERTED]) - - -RC_SWITCH_RAW_SCHEMA = cv.Schema({ - cv.Required(CONF_CODE): validate_rc_switch_raw_code, - cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, -}) -RC_SWITCH_TYPE_A_SCHEMA = cv.Schema({ - cv.Required(CONF_GROUP): cv.All(validate_rc_switch_code, cv.Length(min=5, max=5)), - cv.Required(CONF_DEVICE): cv.All(validate_rc_switch_code, cv.Length(min=5, max=5)), - cv.Required(CONF_STATE): cv.boolean, - cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, -}) -RC_SWITCH_TYPE_B_SCHEMA = cv.Schema({ - 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.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.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='0us'): - cv.templatable(cv.positive_time_period_microseconds), - }), -}) + return RCSwitchBase( + config[CONF_SYNC][0] * pl, + config[CONF_SYNC][1] * pl, + config[CONF_ZERO][0] * pl, + config[CONF_ZERO][1] * pl, + config[CONF_ONE][0] * pl, + config[CONF_ONE][1] * pl, + config[CONF_INVERTED], + ) + + +RC_SWITCH_RAW_SCHEMA = cv.Schema( + { + cv.Required(CONF_CODE): validate_rc_switch_raw_code, + cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, + } +) +RC_SWITCH_TYPE_A_SCHEMA = cv.Schema( + { + cv.Required(CONF_GROUP): cv.All( + validate_rc_switch_code, cv.Length(min=5, max=5) + ), + cv.Required(CONF_DEVICE): cv.All( + validate_rc_switch_code, cv.Length(min=5, max=5) + ), + cv.Required(CONF_STATE): cv.boolean, + cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, + } +) +RC_SWITCH_TYPE_B_SCHEMA = cv.Schema( + { + 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.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.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="0us"): cv.templatable( + cv.positive_time_period_microseconds + ), + } + ), + } +) rc_switch_protocols = ns.rc_switch_protocols -RCSwitchData = ns.struct('RCSwitchData') -RCSwitchBase = ns.class_('RCSwitchBase') -RCSwitchTrigger = ns.class_('RCSwitchTrigger', RemoteReceiverTrigger) -RCSwitchDumper = ns.class_('RCSwitchDumper', RemoteTransmitterDumper) -RCSwitchRawAction = ns.class_('RCSwitchRawAction', RemoteTransmitterActionBase) -RCSwitchTypeAAction = ns.class_('RCSwitchTypeAAction', RemoteTransmitterActionBase) -RCSwitchTypeBAction = ns.class_('RCSwitchTypeBAction', RemoteTransmitterActionBase) -RCSwitchTypeCAction = ns.class_('RCSwitchTypeCAction', RemoteTransmitterActionBase) -RCSwitchTypeDAction = ns.class_('RCSwitchTypeDAction', RemoteTransmitterActionBase) -RCSwitchRawReceiver = ns.class_('RCSwitchRawReceiver', RemoteReceiverBinarySensorBase) - - -@register_binary_sensor('rc_switch_raw', RCSwitchRawReceiver, RC_SWITCH_RAW_SCHEMA) +RCSwitchData = ns.struct("RCSwitchData") +RCSwitchBase = ns.class_("RCSwitchBase") +RCSwitchTrigger = ns.class_("RCSwitchTrigger", RemoteReceiverTrigger) +RCSwitchDumper = ns.class_("RCSwitchDumper", RemoteTransmitterDumper) +RCSwitchRawAction = ns.class_("RCSwitchRawAction", RemoteTransmitterActionBase) +RCSwitchTypeAAction = ns.class_("RCSwitchTypeAAction", RemoteTransmitterActionBase) +RCSwitchTypeBAction = ns.class_("RCSwitchTypeBAction", RemoteTransmitterActionBase) +RCSwitchTypeCAction = ns.class_("RCSwitchTypeCAction", RemoteTransmitterActionBase) +RCSwitchTypeDAction = ns.class_("RCSwitchTypeDAction", RemoteTransmitterActionBase) +RCSwitchRawReceiver = ns.class_("RCSwitchRawReceiver", RemoteReceiverBinarySensorBase) + + +@register_binary_sensor("rc_switch_raw", RCSwitchRawReceiver, RC_SWITCH_RAW_SCHEMA) def rc_switch_raw_binary_sensor(var, config): cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) cg.add(var.set_code(config[CONF_CODE])) -@register_action('rc_switch_raw', RCSwitchRawAction, - RC_SWITCH_RAW_SCHEMA.extend(RC_SWITCH_TRANSMITTER)) +@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) + proto = yield cg.templatable( + config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol + ) cg.add(var.set_protocol(proto)) cg.add(var.set_code((yield cg.templatable(config[CONF_CODE], args, cg.std_string)))) -@register_binary_sensor('rc_switch_type_a', RCSwitchRawReceiver, RC_SWITCH_TYPE_A_SCHEMA) +@register_binary_sensor( + "rc_switch_type_a", RCSwitchRawReceiver, RC_SWITCH_TYPE_A_SCHEMA +) def rc_switch_type_a_binary_sensor(var, config): cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) 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.extend(RC_SWITCH_TRANSMITTER)) +@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) + proto = yield cg.templatable( + config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol + ) cg.add(var.set_protocol(proto)) - cg.add(var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.std_string)))) - cg.add(var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.std_string)))) + cg.add( + var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.std_string))) + ) + cg.add( + var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.std_string))) + ) cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, bool)))) -@register_binary_sensor('rc_switch_type_b', RCSwitchRawReceiver, RC_SWITCH_TYPE_B_SCHEMA) +@register_binary_sensor( + "rc_switch_type_b", RCSwitchRawReceiver, RC_SWITCH_TYPE_B_SCHEMA +) def rc_switch_type_b_binary_sensor(var, config): cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) - cg.add(var.set_type_b(config[CONF_ADDRESS], config[CONF_CHANNEL], config[CONF_STATE])) + 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.extend(RC_SWITCH_TRANSMITTER)) +@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) + proto = yield cg.templatable( + config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol + ) cg.add(var.set_protocol(proto)) - cg.add(var.set_address((yield cg.templatable(config[CONF_ADDRESS], args, cg.uint8)))) - cg.add(var.set_channel((yield cg.templatable(config[CONF_CHANNEL], args, cg.uint8)))) + cg.add( + var.set_address((yield cg.templatable(config[CONF_ADDRESS], args, cg.uint8))) + ) + cg.add( + var.set_channel((yield cg.templatable(config[CONF_CHANNEL], args, cg.uint8))) + ) cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, bool)))) -@register_binary_sensor('rc_switch_type_c', RCSwitchRawReceiver, RC_SWITCH_TYPE_C_SCHEMA) +@register_binary_sensor( + "rc_switch_type_c", RCSwitchRawReceiver, RC_SWITCH_TYPE_C_SCHEMA +) def rc_switch_type_c_binary_sensor(var, config): cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) - cg.add(var.set_type_c(config[CONF_FAMILY], config[CONF_GROUP], config[CONF_DEVICE], - config[CONF_STATE])) - - -@register_action('rc_switch_type_c', RCSwitchTypeCAction, - RC_SWITCH_TYPE_C_SCHEMA.extend(RC_SWITCH_TRANSMITTER)) + cg.add( + var.set_type_c( + config[CONF_FAMILY], + config[CONF_GROUP], + config[CONF_DEVICE], + config[CONF_STATE], + ) + ) + + +@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) + proto = yield cg.templatable( + config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol + ) cg.add(var.set_protocol(proto)) - cg.add(var.set_family((yield cg.templatable(config[CONF_FAMILY], args, cg.std_string)))) + cg.add( + var.set_family((yield cg.templatable(config[CONF_FAMILY], args, cg.std_string))) + ) cg.add(var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.uint8)))) cg.add(var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.uint8)))) 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.extend(RC_SWITCH_TRANSMITTER)) +@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.extend(RC_SWITCH_TRANSMITTER)) +@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) + proto = yield cg.templatable( + config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol + ) cg.add(var.set_protocol(proto)) - cg.add(var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.std_string)))) + cg.add( + var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.std_string))) + ) cg.add(var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.uint8)))) cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, bool)))) -@register_trigger('rc_switch', RCSwitchTrigger, RCSwitchData) +@register_trigger("rc_switch", RCSwitchTrigger, RCSwitchData) def rc_switch_trigger(var, config): pass -@register_dumper('rc_switch', RCSwitchDumper) +@register_dumper("rc_switch", RCSwitchDumper) def rc_switch_dumper(var, config): pass # Samsung -(SamsungData, SamsungBinarySensor, SamsungTrigger, SamsungAction, - SamsungDumper) = declare_protocol('Samsung') -SAMSUNG_SCHEMA = cv.Schema({ - cv.Required(CONF_DATA): cv.hex_uint32_t, -}) +( + SamsungData, + SamsungBinarySensor, + SamsungTrigger, + SamsungAction, + SamsungDumper, +) = declare_protocol("Samsung") +SAMSUNG_SCHEMA = cv.Schema( + { + cv.Required(CONF_DATA): cv.hex_uint32_t, + } +) -@register_binary_sensor('samsung', SamsungBinarySensor, SAMSUNG_SCHEMA) +@register_binary_sensor("samsung", SamsungBinarySensor, SAMSUNG_SCHEMA) def samsung_binary_sensor(var, config): - cg.add(var.set_data(cg.StructInitializer( - SamsungData, - ('data', config[CONF_DATA]), - ))) + cg.add( + var.set_data( + cg.StructInitializer( + SamsungData, + ("data", config[CONF_DATA]), + ) + ) + ) -@register_trigger('samsung', SamsungTrigger, SamsungData) +@register_trigger("samsung", SamsungTrigger, SamsungData) def samsung_trigger(var, config): pass -@register_dumper('samsung', SamsungDumper) +@register_dumper("samsung", SamsungDumper) def samsung_dumper(var, config): pass -@register_action('samsung', SamsungAction, SAMSUNG_SCHEMA) +@register_action("samsung", SamsungAction, SAMSUNG_SCHEMA) def samsung_action(var, config, args): template_ = yield cg.templatable(config[CONF_DATA], args, cg.uint32) cg.add(var.set_data(template_)) -# Panasonic -(PanasonicData, PanasonicBinarySensor, PanasonicTrigger, PanasonicAction, - PanasonicDumper) = declare_protocol('Panasonic') -PANASONIC_SCHEMA = cv.Schema({ - cv.Required(CONF_ADDRESS): cv.hex_uint16_t, - cv.Required(CONF_COMMAND): cv.hex_uint32_t, -}) +# Samsung36 +( + Samsung36Data, + Samsung36BinarySensor, + Samsung36Trigger, + Samsung36Action, + Samsung36Dumper, +) = declare_protocol("Samsung36") +SAMSUNG36_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.hex_uint16_t, + cv.Required(CONF_COMMAND): cv.hex_uint32_t, + } +) -@register_binary_sensor('panasonic', PanasonicBinarySensor, PANASONIC_SCHEMA) -def panasonic_binary_sensor(var, config): - cg.add(var.set_data(cg.StructInitializer( - PanasonicData, - ('address', config[CONF_ADDRESS]), - ('command', config[CONF_COMMAND]), - ))) +@register_binary_sensor("samsung36", Samsung36BinarySensor, SAMSUNG36_SCHEMA) +def samsung36_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + Samsung36Data, + ("address", config[CONF_ADDRESS]), + ("command", config[CONF_COMMAND]), + ) + ) + ) -@register_trigger('panasonic', PanasonicTrigger, PanasonicData) +@register_trigger("samsung36", Samsung36Trigger, Samsung36Data) +def samsung36_trigger(var, config): + pass + + +@register_dumper("samsung36", Samsung36Dumper) +def samsung36_dumper(var, config): + pass + + +@register_action("samsung36", Samsung36Action, SAMSUNG36_SCHEMA) +def samsung36_action(var, config, args): + template_ = yield cg.templatable(config[CONF_ADDRESS], args, cg.uint16) + cg.add(var.set_address(template_)) + template_ = yield cg.templatable(config[CONF_COMMAND], args, cg.uint32) + cg.add(var.set_command(template_)) + + +# Panasonic +( + PanasonicData, + PanasonicBinarySensor, + PanasonicTrigger, + PanasonicAction, + PanasonicDumper, +) = declare_protocol("Panasonic") +PANASONIC_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.hex_uint16_t, + cv.Required(CONF_COMMAND): cv.hex_uint32_t, + } +) + + +@register_binary_sensor("panasonic", PanasonicBinarySensor, PANASONIC_SCHEMA) +def panasonic_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + PanasonicData, + ("address", config[CONF_ADDRESS]), + ("command", config[CONF_COMMAND]), + ) + ) + ) + + +@register_trigger("panasonic", PanasonicTrigger, PanasonicData) def panasonic_trigger(var, config): pass -@register_dumper('panasonic', PanasonicDumper) +@register_dumper("panasonic", PanasonicDumper) def panasonic_dumper(var, config): pass -@register_action('panasonic', PanasonicAction, PANASONIC_SCHEMA) +@register_action("panasonic", PanasonicAction, PANASONIC_SCHEMA) def panasonic_action(var, config, args): template_ = yield cg.templatable(config[CONF_ADDRESS], args, cg.uint16) cg.add(var.set_address(template_)) diff --git a/esphome/components/remote_base/samsung36_protocol.cpp b/esphome/components/remote_base/samsung36_protocol.cpp new file mode 100644 index 000000000000..22b158fd8a56 --- /dev/null +++ b/esphome/components/remote_base/samsung36_protocol.cpp @@ -0,0 +1,103 @@ +#include "samsung36_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *TAG = "remote.samsung36"; + +static const uint8_t NBITS = 78; + +static const uint32_t HEADER_HIGH_US = 4500; +static const uint32_t HEADER_LOW_US = 4500; +static const uint32_t BIT_HIGH_US = 500; +static const uint32_t BIT_ONE_LOW_US = 1500; +static const uint32_t BIT_ZERO_LOW_US = 500; +static const uint32_t MIDDLE_HIGH_US = 500; +static const uint32_t MIDDLE_LOW_US = 4500; +static const uint32_t FOOTER_HIGH_US = 500; +static const uint32_t FOOTER_LOW_US = 59000; + +void Samsung36Protocol::encode(RemoteTransmitData *dst, const Samsung36Data &data) { + dst->set_carrier_frequency(38000); + dst->reserve(NBITS); + + // send header + dst->item(HEADER_HIGH_US, HEADER_LOW_US); + + // send first 16 bits + for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) { + if (data.address & mask) { + dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); + } else { + dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } + } + + // send middle header + dst->item(MIDDLE_HIGH_US, MIDDLE_LOW_US); + + // send last 20 bits + for (uint32_t mask = 1UL << 19; mask != 0; mask >>= 1) { + if (data.command & mask) { + dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); + } else { + dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } + } + + // footer + dst->item(FOOTER_HIGH_US, FOOTER_LOW_US); +} + +optional Samsung36Protocol::decode(RemoteReceiveData src) { + Samsung36Data out{ + .address = 0, + .command = 0, + }; + + // check if header matches + if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) + return {}; + + // check if we have enough bits + if (src.size() != NBITS) + return {}; + + // get the first 16 bits + for (uint8_t i = 0; i < 16; i++) { + out.address <<= 1UL; + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + out.address |= 1UL; + } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + out.address |= 0UL; + } else { + return {}; + } + } + + // check if the middle mark matches + if (!src.expect_item(MIDDLE_HIGH_US, MIDDLE_LOW_US)) { + return {}; + } + + // get the last 20 bits + for (uint8_t i = 0; i < 20; i++) { + out.command <<= 1UL; + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + out.command |= 1UL; + } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + out.command |= 0UL; + } else { + return {}; + } + } + + return out; +} +void Samsung36Protocol::dump(const Samsung36Data &data) { + ESP_LOGD(TAG, "Received Samsung36: address=0x%04X, command=0x%08X", data.address, data.command); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/samsung36_protocol.h b/esphome/components/remote_base/samsung36_protocol.h new file mode 100644 index 000000000000..4ba6226edd19 --- /dev/null +++ b/esphome/components/remote_base/samsung36_protocol.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct Samsung36Data { + uint16_t address; + uint32_t command; + + bool operator==(const Samsung36Data &rhs) const { return address == rhs.address && command == rhs.command; } +}; + +class Samsung36Protocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const Samsung36Data &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const Samsung36Data &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Samsung36) + +template class Samsung36Action : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint16_t, address) + TEMPLATABLE_VALUE(uint32_t, command) + + void encode(RemoteTransmitData *dst, Ts... x) override { + Samsung36Data data{}; + data.address = this->address_.value(x...); + data.command = this->command_.value(x...); + Samsung36Protocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index 28570a7c6202..f29c59b3f809 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -2,28 +2,49 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import remote_base -from esphome.const import CONF_BUFFER_SIZE, CONF_DUMP, CONF_FILTER, CONF_ID, CONF_IDLE, \ - CONF_PIN, CONF_TOLERANCE, CONF_MEMORY_BLOCKS +from esphome.const import ( + CONF_BUFFER_SIZE, + CONF_DUMP, + CONF_FILTER, + CONF_ID, + CONF_IDLE, + CONF_PIN, + CONF_TOLERANCE, + CONF_MEMORY_BLOCKS, +) from esphome.core import CORE -AUTO_LOAD = ['remote_base'] -remote_receiver_ns = cg.esphome_ns.namespace('remote_receiver') -RemoteReceiverComponent = remote_receiver_ns.class_('RemoteReceiverComponent', - remote_base.RemoteReceiverBase, - cg.Component) +AUTO_LOAD = ["remote_base"] +remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver") +RemoteReceiverComponent = remote_receiver_ns.class_( + "RemoteReceiverComponent", remote_base.RemoteReceiverBase, cg.Component +) MULTI_CONF = True -CONFIG_SCHEMA = remote_base.validate_triggers(cv.Schema({ - cv.GenerateID(): cv.declare_id(RemoteReceiverComponent), - cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema, - pins.validate_has_interrupt), - cv.Optional(CONF_DUMP, default=[]): remote_base.validate_dumpers, - cv.Optional(CONF_TOLERANCE, default=25): cv.All(cv.percentage_int, cv.Range(min=0)), - cv.SplitDefault(CONF_BUFFER_SIZE, esp32='10000b', esp8266='1000b'): cv.validate_bytes, - cv.Optional(CONF_FILTER, default='50us'): cv.positive_time_period_microseconds, - cv.Optional(CONF_IDLE, default='10ms'): cv.positive_time_period_microseconds, - cv.Optional(CONF_MEMORY_BLOCKS, default=3): cv.Range(min=1, max=8), -}).extend(cv.COMPONENT_SCHEMA)) +CONFIG_SCHEMA = remote_base.validate_triggers( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RemoteReceiverComponent), + cv.Required(CONF_PIN): cv.All( + pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt + ), + cv.Optional(CONF_DUMP, default=[]): remote_base.validate_dumpers, + cv.Optional(CONF_TOLERANCE, default=25): cv.All( + cv.percentage_int, cv.Range(min=0) + ), + cv.SplitDefault( + CONF_BUFFER_SIZE, esp32="10000b", esp8266="1000b" + ): cv.validate_bytes, + cv.Optional( + CONF_FILTER, default="50us" + ): cv.positive_time_period_microseconds, + cv.Optional( + CONF_IDLE, default="10ms" + ): cv.positive_time_period_microseconds, + cv.Optional(CONF_MEMORY_BLOCKS, default=3): cv.Range(min=1, max=8), + } + ).extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/remote_receiver/binary_sensor.py b/esphome/components/remote_receiver/binary_sensor.py index 7be64e5a5433..522333584c33 100644 --- a/esphome/components/remote_receiver/binary_sensor.py +++ b/esphome/components/remote_receiver/binary_sensor.py @@ -2,7 +2,7 @@ from esphome.components import binary_sensor, remote_base from esphome.const import CONF_NAME -DEPENDENCIES = ['remote_receiver'] +DEPENDENCIES = ["remote_receiver"] CONFIG_SCHEMA = remote_base.validate_binary_sensor diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index 5e217de60877..123af7db9771 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -4,18 +4,22 @@ from esphome.components import remote_base from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN -AUTO_LOAD = ['remote_base'] -remote_transmitter_ns = cg.esphome_ns.namespace('remote_transmitter') -RemoteTransmitterComponent = remote_transmitter_ns.class_('RemoteTransmitterComponent', - remote_base.RemoteTransmitterBase, - cg.Component) +AUTO_LOAD = ["remote_base"] +remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter") +RemoteTransmitterComponent = remote_transmitter_ns.class_( + "RemoteTransmitterComponent", remote_base.RemoteTransmitterBase, cg.Component +) MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(RemoteTransmitterComponent), - cv.Required(CONF_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All(cv.percentage_int, cv.Range(min=1, max=100)), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(RemoteTransmitterComponent), + cv.Required(CONF_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All( + cv.percentage_int, cv.Range(min=1, max=100) + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/remote_transmitter/switch.py b/esphome/components/remote_transmitter/switch.py index 5e0be04d7a11..3a2e43a31ad3 100644 --- a/esphome/components/remote_transmitter/switch.py +++ b/esphome/components/remote_transmitter/switch.py @@ -5,26 +5,29 @@ def show_new(value): from esphome import yaml_util + for key in BINARY_SENSOR_REGISTRY: if key in value: break else: - raise cv.Invalid("This platform has been removed in 1.13, please see the docs for updated " - "instructions.") + raise cv.Invalid( + "This platform has been removed in 1.13, please see the docs for updated " + "instructions." + ) val = value[key] - args = [('platform', 'template')] - if 'id' in value: - args.append(('id', value['id'])) - if 'name' in value: - args.append(('name', value['name'])) - args.append(('turn_on_action', { - f'remote_transmitter.transmit_{key}': val - })) + args = [("platform", "template")] + if "id" in value: + args.append(("id", value["id"])) + if "name" in value: + args.append(("name", value["name"])) + args.append(("turn_on_action", {f"remote_transmitter.transmit_{key}": val})) text = yaml_util.dump([OrderedDict(args)]) - raise cv.Invalid("This platform has been removed in 1.13, please change to:\n\n{}\n\n." - "".format(text)) + raise cv.Invalid( + "This platform has been removed in 1.13, please change to:\n\n{}\n\n." + "".format(text) + ) CONFIG_SCHEMA = show_new diff --git a/esphome/components/resistance/sensor.py b/esphome/components/resistance/sensor.py index fb245bcdf038..92a564f68edd 100644 --- a/esphome/components/resistance/sensor.py +++ b/esphome/components/resistance/sensor.py @@ -1,28 +1,34 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import CONF_SENSOR, UNIT_OHM, ICON_FLASH, CONF_ID +from esphome.const import CONF_SENSOR, DEVICE_CLASS_EMPTY, UNIT_OHM, ICON_FLASH, CONF_ID -resistance_ns = cg.esphome_ns.namespace('resistance') -ResistanceSensor = resistance_ns.class_('ResistanceSensor', cg.Component, sensor.Sensor) +resistance_ns = cg.esphome_ns.namespace("resistance") +ResistanceSensor = resistance_ns.class_("ResistanceSensor", cg.Component, sensor.Sensor) -CONF_REFERENCE_VOLTAGE = 'reference_voltage' -CONF_CONFIGURATION = 'configuration' -CONF_RESISTOR = 'resistor' +CONF_REFERENCE_VOLTAGE = "reference_voltage" +CONF_CONFIGURATION = "configuration" +CONF_RESISTOR = "resistor" -ResistanceConfiguration = resistance_ns.enum('ResistanceConfiguration') +ResistanceConfiguration = resistance_ns.enum("ResistanceConfiguration") CONFIGURATIONS = { - 'DOWNSTREAM': ResistanceConfiguration.DOWNSTREAM, - 'UPSTREAM': ResistanceConfiguration.UPSTREAM, + "DOWNSTREAM": ResistanceConfiguration.DOWNSTREAM, + "UPSTREAM": ResistanceConfiguration.UPSTREAM, } -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_OHM, ICON_FLASH, 1).extend({ - cv.GenerateID(): cv.declare_id(ResistanceSensor), - cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), - cv.Required(CONF_CONFIGURATION): cv.enum(CONFIGURATIONS, upper=True), - cv.Required(CONF_RESISTOR): cv.resistance, - cv.Optional(CONF_REFERENCE_VOLTAGE, default='3.3V'): cv.voltage, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_OHM, ICON_FLASH, 1, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(ResistanceSensor), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_CONFIGURATION): cv.enum(CONFIGURATIONS, upper=True), + cv.Required(CONF_RESISTOR): cv.resistance, + cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/restart/__init__.py b/esphome/components/restart/__init__.py index 63db7aee2ecd..f70ffa952087 100644 --- a/esphome/components/restart/__init__.py +++ b/esphome/components/restart/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] diff --git a/esphome/components/restart/switch.py b/esphome/components/restart/switch.py index 9517302d336b..fe170aee61ea 100644 --- a/esphome/components/restart/switch.py +++ b/esphome/components/restart/switch.py @@ -3,15 +3,18 @@ from esphome.components import switch from esphome.const import CONF_ID, CONF_INVERTED, CONF_ICON, ICON_RESTART -restart_ns = cg.esphome_ns.namespace('restart') -RestartSwitch = restart_ns.class_('RestartSwitch', switch.Switch, cg.Component) +restart_ns = cg.esphome_ns.namespace("restart") +RestartSwitch = restart_ns.class_("RestartSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(RestartSwitch), - cv.Optional(CONF_INVERTED): cv.invalid("Restart switches do not support inverted mode!"), - - cv.Optional(CONF_ICON, default=ICON_RESTART): switch.icon, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(RestartSwitch), + cv.Optional(CONF_INVERTED): cv.invalid( + "Restart switches do not support inverted mode!" + ), + cv.Optional(CONF_ICON, default=ICON_RESTART): switch.icon, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/rf_bridge/__init__.py b/esphome/components/rf_bridge/__init__.py index a0a910118bf6..b24e19b4bf51 100644 --- a/esphome/components/rf_bridge/__init__.py +++ b/esphome/components/rf_bridge/__init__.py @@ -14,47 +14,69 @@ CONF_TRIGGER_ID, ) -DEPENDENCIES = ['uart'] -CODEOWNERS = ['@jesserockz'] +DEPENDENCIES = ["uart"] +CODEOWNERS = ["@jesserockz"] -rf_bridge_ns = cg.esphome_ns.namespace('rf_bridge') -RFBridgeComponent = rf_bridge_ns.class_('RFBridgeComponent', cg.Component, uart.UARTDevice) +rf_bridge_ns = cg.esphome_ns.namespace("rf_bridge") +RFBridgeComponent = rf_bridge_ns.class_( + "RFBridgeComponent", cg.Component, uart.UARTDevice +) -RFBridgeData = rf_bridge_ns.struct('RFBridgeData') -RFBridgeAdvancedData = rf_bridge_ns.struct('RFBridgeAdvancedData') +RFBridgeData = rf_bridge_ns.struct("RFBridgeData") +RFBridgeAdvancedData = rf_bridge_ns.struct("RFBridgeAdvancedData") -RFBridgeReceivedCodeTrigger = rf_bridge_ns.class_('RFBridgeReceivedCodeTrigger', - automation.Trigger.template(RFBridgeData)) +RFBridgeReceivedCodeTrigger = rf_bridge_ns.class_( + "RFBridgeReceivedCodeTrigger", automation.Trigger.template(RFBridgeData) +) RFBridgeReceivedAdvancedCodeTrigger = rf_bridge_ns.class_( - 'RFBridgeReceivedAdvancedCodeTrigger', + "RFBridgeReceivedAdvancedCodeTrigger", automation.Trigger.template(RFBridgeAdvancedData), ) -RFBridgeSendCodeAction = rf_bridge_ns.class_('RFBridgeSendCodeAction', automation.Action) +RFBridgeSendCodeAction = rf_bridge_ns.class_( + "RFBridgeSendCodeAction", automation.Action +) RFBridgeSendAdvancedCodeAction = rf_bridge_ns.class_( - 'RFBridgeSendAdvancedCodeAction', automation.Action) + "RFBridgeSendAdvancedCodeAction", automation.Action +) -RFBridgeLearnAction = rf_bridge_ns.class_('RFBridgeLearnAction', automation.Action) +RFBridgeLearnAction = rf_bridge_ns.class_("RFBridgeLearnAction", automation.Action) RFBridgeStartAdvancedSniffingAction = rf_bridge_ns.class_( - 'RFBridgeStartAdvancedSniffingAction', automation.Action) + "RFBridgeStartAdvancedSniffingAction", automation.Action +) RFBridgeStopAdvancedSniffingAction = rf_bridge_ns.class_( - 'RFBridgeStopAdvancedSniffingAction', automation.Action) - -RFBridgeSendRawAction = rf_bridge_ns.class_('RFBridgeSendRawAction', automation.Action) - -CONF_ON_CODE_RECEIVED = 'on_code_received' -CONF_ON_ADVANCED_CODE_RECEIVED = 'on_advanced_code_received' + "RFBridgeStopAdvancedSniffingAction", automation.Action +) -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(RFBridgeComponent), - cv.Optional(CONF_ON_CODE_RECEIVED): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RFBridgeReceivedCodeTrigger), - }), - cv.Optional(CONF_ON_ADVANCED_CODE_RECEIVED): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RFBridgeReceivedAdvancedCodeTrigger), - }), -}).extend(uart.UART_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)) +RFBridgeSendRawAction = rf_bridge_ns.class_("RFBridgeSendRawAction", automation.Action) + +CONF_ON_CODE_RECEIVED = "on_code_received" +CONF_ON_ADVANCED_CODE_RECEIVED = "on_advanced_code_received" + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RFBridgeComponent), + cv.Optional(CONF_ON_CODE_RECEIVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + RFBridgeReceivedCodeTrigger + ), + } + ), + cv.Optional(CONF_ON_ADVANCED_CODE_RECEIVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + RFBridgeReceivedAdvancedCodeTrigger + ), + } + ), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): @@ -64,26 +86,29 @@ def to_code(config): for conf in config.get(CONF_ON_CODE_RECEIVED, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - yield automation.build_automation(trigger, [(RFBridgeData, 'data')], conf) + yield automation.build_automation(trigger, [(RFBridgeData, "data")], conf) for conf in config.get(CONF_ON_ADVANCED_CODE_RECEIVED, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) yield automation.build_automation( - trigger, [(RFBridgeAdvancedData, 'data')], conf + trigger, [(RFBridgeAdvancedData, "data")], conf ) -RFBRIDGE_SEND_CODE_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.use_id(RFBridgeComponent), - cv.Required(CONF_SYNC): cv.templatable(cv.hex_uint16_t), - cv.Required(CONF_LOW): cv.templatable(cv.hex_uint16_t), - cv.Required(CONF_HIGH): cv.templatable(cv.hex_uint16_t), - cv.Required(CONF_CODE): cv.templatable(cv.hex_uint32_t) -}) +RFBRIDGE_SEND_CODE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(RFBridgeComponent), + cv.Required(CONF_SYNC): cv.templatable(cv.hex_uint16_t), + cv.Required(CONF_LOW): cv.templatable(cv.hex_uint16_t), + cv.Required(CONF_HIGH): cv.templatable(cv.hex_uint16_t), + cv.Required(CONF_CODE): cv.templatable(cv.hex_uint32_t), + } +) -@automation.register_action('rf_bridge.send_code', RFBridgeSendCodeAction, - RFBRIDGE_SEND_CODE_SCHEMA) +@automation.register_action( + "rf_bridge.send_code", RFBridgeSendCodeAction, RFBRIDGE_SEND_CODE_SCHEMA +) def rf_bridge_send_code_to_code(config, action_id, template_args, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_args, paren) @@ -98,12 +123,10 @@ def rf_bridge_send_code_to_code(config, action_id, template_args, args): yield var -RFBRIDGE_ID_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.use_id(RFBridgeComponent) -}) +RFBRIDGE_ID_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(RFBridgeComponent)}) -@automation.register_action('rf_bridge.learn', RFBridgeLearnAction, RFBRIDGE_ID_SCHEMA) +@automation.register_action("rf_bridge.learn", RFBridgeLearnAction, RFBRIDGE_ID_SCHEMA) def rf_bridge_learnx_to_code(config, action_id, template_args, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_args, paren) @@ -111,7 +134,7 @@ def rf_bridge_learnx_to_code(config, action_id, template_args, args): @automation.register_action( - 'rf_bridge.start_advanced_sniffing', + "rf_bridge.start_advanced_sniffing", RFBridgeStartAdvancedSniffingAction, RFBRIDGE_ID_SCHEMA, ) @@ -122,7 +145,7 @@ def rf_bridge_start_advanced_sniffing_to_code(config, action_id, template_args, @automation.register_action( - 'rf_bridge.stop_advanced_sniffing', + "rf_bridge.stop_advanced_sniffing", RFBridgeStopAdvancedSniffingAction, RFBRIDGE_ID_SCHEMA, ) @@ -132,18 +155,20 @@ def rf_bridge_stop_advanced_sniffing_to_code(config, action_id, template_args, a yield var -RFBRIDGE_SEND_ADVANCED_CODE_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.use_id(RFBridgeComponent), - cv.Required(CONF_LENGTH): cv.templatable(cv.hex_uint8_t), - cv.Required(CONF_PROTOCOL): cv.templatable(cv.hex_uint8_t), - cv.Required(CONF_CODE): cv.templatable(cv.string), -}) +RFBRIDGE_SEND_ADVANCED_CODE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(RFBridgeComponent), + cv.Required(CONF_LENGTH): cv.templatable(cv.hex_uint8_t), + cv.Required(CONF_PROTOCOL): cv.templatable(cv.hex_uint8_t), + cv.Required(CONF_CODE): cv.templatable(cv.string), + } +) @automation.register_action( - 'rf_bridge.send_advanced_code', + "rf_bridge.send_advanced_code", RFBridgeSendAdvancedCodeAction, - RFBRIDGE_SEND_ADVANCED_CODE_SCHEMA + RFBRIDGE_SEND_ADVANCED_CODE_SCHEMA, ) def rf_bridge_send_advanced_code_to_code(config, action_id, template_args, args): paren = yield cg.get_variable(config[CONF_ID]) @@ -166,9 +191,7 @@ def rf_bridge_send_advanced_code_to_code(config, action_id, template_args, args) @automation.register_action( - 'rf_bridge.send_raw', - RFBridgeSendRawAction, - RFBRIDGE_SEND_RAW_SCHEMA + "rf_bridge.send_raw", RFBridgeSendRawAction, RFBRIDGE_SEND_RAW_SCHEMA ) def rf_bridge_send_raw_to_code(config, action_id, template_args, args): paren = yield cg.get_variable(config[CONF_ID]) diff --git a/esphome/components/rgb/light.py b/esphome/components/rgb/light.py index 6bece17664a4..33e381d59eff 100644 --- a/esphome/components/rgb/light.py +++ b/esphome/components/rgb/light.py @@ -3,15 +3,17 @@ from esphome.components import light, output from esphome.const import CONF_BLUE, CONF_GREEN, CONF_RED, CONF_OUTPUT_ID -rgb_ns = cg.esphome_ns.namespace('rgb') -RGBLightOutput = rgb_ns.class_('RGBLightOutput', light.LightOutput) +rgb_ns = cg.esphome_ns.namespace("rgb") +RGBLightOutput = rgb_ns.class_("RGBLightOutput", light.LightOutput) -CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBLightOutput), - cv.Required(CONF_RED): cv.use_id(output.FloatOutput), - cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), - cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), -}) +CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBLightOutput), + cv.Required(CONF_RED): cv.use_id(output.FloatOutput), + cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), + cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), + } +) def to_code(config): diff --git a/esphome/components/rgbw/light.py b/esphome/components/rgbw/light.py index ca31a8229d81..a922a7de37ff 100644 --- a/esphome/components/rgbw/light.py +++ b/esphome/components/rgbw/light.py @@ -3,18 +3,20 @@ from esphome.components import light, output from esphome.const import CONF_BLUE, CONF_GREEN, CONF_RED, CONF_OUTPUT_ID, CONF_WHITE -rgbw_ns = cg.esphome_ns.namespace('rgbw') -RGBWLightOutput = rgbw_ns.class_('RGBWLightOutput', light.LightOutput) -CONF_COLOR_INTERLOCK = 'color_interlock' +rgbw_ns = cg.esphome_ns.namespace("rgbw") +RGBWLightOutput = rgbw_ns.class_("RGBWLightOutput", light.LightOutput) +CONF_COLOR_INTERLOCK = "color_interlock" -CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBWLightOutput), - cv.Required(CONF_RED): cv.use_id(output.FloatOutput), - cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), - cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), - cv.Required(CONF_WHITE): cv.use_id(output.FloatOutput), - cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, -}) +CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBWLightOutput), + cv.Required(CONF_RED): cv.use_id(output.FloatOutput), + cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), + cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), + cv.Required(CONF_WHITE): cv.use_id(output.FloatOutput), + cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, + } +) def to_code(config): diff --git a/esphome/components/rgbww/light.py b/esphome/components/rgbww/light.py index 1513a684ea24..d7d0d7fb1502 100644 --- a/esphome/components/rgbww/light.py +++ b/esphome/components/rgbww/light.py @@ -1,28 +1,37 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light, output -from esphome.const import CONF_BLUE, CONF_GREEN, CONF_RED, CONF_OUTPUT_ID, CONF_COLD_WHITE, \ - CONF_WARM_WHITE, CONF_COLD_WHITE_COLOR_TEMPERATURE, \ - CONF_WARM_WHITE_COLOR_TEMPERATURE - -rgbww_ns = cg.esphome_ns.namespace('rgbww') -RGBWWLightOutput = rgbww_ns.class_('RGBWWLightOutput', light.LightOutput) - -CONF_CONSTANT_BRIGHTNESS = 'constant_brightness' -CONF_COLOR_INTERLOCK = 'color_interlock' - -CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBWWLightOutput), - cv.Required(CONF_RED): cv.use_id(output.FloatOutput), - cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), - cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), - cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), - cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), - cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, - cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, - cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, - cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, -}) +from esphome.const import ( + CONF_BLUE, + CONF_GREEN, + CONF_RED, + CONF_OUTPUT_ID, + CONF_COLD_WHITE, + CONF_WARM_WHITE, + CONF_COLD_WHITE_COLOR_TEMPERATURE, + CONF_WARM_WHITE_COLOR_TEMPERATURE, +) + +rgbww_ns = cg.esphome_ns.namespace("rgbww") +RGBWWLightOutput = rgbww_ns.class_("RGBWWLightOutput", light.LightOutput) + +CONF_CONSTANT_BRIGHTNESS = "constant_brightness" +CONF_COLOR_INTERLOCK = "color_interlock" + +CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBWWLightOutput), + cv.Required(CONF_RED): cv.use_id(output.FloatOutput), + cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), + cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), + cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), + cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), + cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, + cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, + } +) def to_code(config): diff --git a/esphome/components/rgbww/rgbww_light_output.h b/esphome/components/rgbww/rgbww_light_output.h index 152766970e76..e14b967530ab 100644 --- a/esphome/components/rgbww/rgbww_light_output.h +++ b/esphome/components/rgbww/rgbww_light_output.h @@ -14,8 +14,8 @@ class RGBWWLightOutput : public light::LightOutput { void set_blue(output::FloatOutput *blue) { blue_ = blue; } void set_cold_white(output::FloatOutput *cold_white) { cold_white_ = cold_white; } void set_warm_white(output::FloatOutput *warm_white) { warm_white_ = warm_white; } - void set_cold_white_temperature(float cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; } - void set_warm_white_temperature(float warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; } + void set_cold_white_temperature(int cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; } + void set_warm_white_temperature(int warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; } void set_constant_brightness(bool constant_brightness) { constant_brightness_ = constant_brightness; } void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } light::LightTraits get_traits() override { @@ -46,8 +46,8 @@ class RGBWWLightOutput : public light::LightOutput { output::FloatOutput *blue_; output::FloatOutput *cold_white_; output::FloatOutput *warm_white_; - float cold_white_temperature_; - float warm_white_temperature_; + int cold_white_temperature_; + int warm_white_temperature_; bool constant_brightness_; bool color_interlock_{false}; }; diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index c518982bc6c2..bcc4a5c930b1 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -2,29 +2,45 @@ import esphome.config_validation as cv from esphome import pins, automation from esphome.components import sensor -from esphome.const import CONF_ID, CONF_RESOLUTION, CONF_MIN_VALUE, CONF_MAX_VALUE, UNIT_STEPS, \ - ICON_ROTATE_RIGHT, CONF_VALUE, CONF_PIN_A, CONF_PIN_B, CONF_TRIGGER_ID +from esphome.const import ( + CONF_ID, + CONF_RESOLUTION, + CONF_MIN_VALUE, + CONF_MAX_VALUE, + DEVICE_CLASS_EMPTY, + UNIT_STEPS, + ICON_ROTATE_RIGHT, + CONF_VALUE, + CONF_PIN_A, + CONF_PIN_B, + CONF_TRIGGER_ID, +) -rotary_encoder_ns = cg.esphome_ns.namespace('rotary_encoder') -RotaryEncoderResolution = rotary_encoder_ns.enum('RotaryEncoderResolution') +rotary_encoder_ns = cg.esphome_ns.namespace("rotary_encoder") +RotaryEncoderResolution = rotary_encoder_ns.enum("RotaryEncoderResolution") RESOLUTIONS = { 1: RotaryEncoderResolution.ROTARY_ENCODER_1_PULSE_PER_CYCLE, 2: RotaryEncoderResolution.ROTARY_ENCODER_2_PULSES_PER_CYCLE, 4: RotaryEncoderResolution.ROTARY_ENCODER_4_PULSES_PER_CYCLE, } -CONF_PIN_RESET = 'pin_reset' -CONF_ON_CLOCKWISE = 'on_clockwise' -CONF_ON_ANTICLOCKWISE = 'on_anticlockwise' +CONF_PIN_RESET = "pin_reset" +CONF_ON_CLOCKWISE = "on_clockwise" +CONF_ON_ANTICLOCKWISE = "on_anticlockwise" -RotaryEncoderSensor = rotary_encoder_ns.class_('RotaryEncoderSensor', sensor.Sensor, cg.Component) -RotaryEncoderSetValueAction = rotary_encoder_ns.class_('RotaryEncoderSetValueAction', - automation.Action) +RotaryEncoderSensor = rotary_encoder_ns.class_( + "RotaryEncoderSensor", sensor.Sensor, cg.Component +) +RotaryEncoderSetValueAction = rotary_encoder_ns.class_( + "RotaryEncoderSetValueAction", automation.Action +) -RotaryEncoderClockwiseTrigger = rotary_encoder_ns.class_('RotaryEncoderClockwiseTrigger', - automation.Trigger) -RotaryEncoderAnticlockwiseTrigger = rotary_encoder_ns.class_('RotaryEncoderAnticlockwiseTrigger', - automation.Trigger) +RotaryEncoderClockwiseTrigger = rotary_encoder_ns.class_( + "RotaryEncoderClockwiseTrigger", automation.Trigger +) +RotaryEncoderAnticlockwiseTrigger = rotary_encoder_ns.class_( + "RotaryEncoderAnticlockwiseTrigger", automation.Trigger +) def validate_min_max_value(config): @@ -32,28 +48,47 @@ def validate_min_max_value(config): min_val = config[CONF_MIN_VALUE] max_val = config[CONF_MAX_VALUE] if min_val >= max_val: - raise cv.Invalid("Max value {} must be smaller than min value {}" - "".format(max_val, min_val)) + raise cv.Invalid( + "Max value {} must be smaller than min value {}" + "".format(max_val, min_val) + ) return config -CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_STEPS, ICON_ROTATE_RIGHT, 0).extend({ - cv.GenerateID(): cv.declare_id(RotaryEncoderSensor), - cv.Required(CONF_PIN_A): cv.All(pins.internal_gpio_input_pin_schema, - pins.validate_has_interrupt), - cv.Required(CONF_PIN_B): cv.All(pins.internal_gpio_input_pin_schema, - pins.validate_has_interrupt), - cv.Optional(CONF_PIN_RESET): pins.internal_gpio_output_pin_schema, - cv.Optional(CONF_RESOLUTION, default=1): cv.enum(RESOLUTIONS, int=True), - cv.Optional(CONF_MIN_VALUE): cv.int_, - cv.Optional(CONF_MAX_VALUE): cv.int_, - cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RotaryEncoderClockwiseTrigger), - }), - cv.Optional(CONF_ON_ANTICLOCKWISE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RotaryEncoderAnticlockwiseTrigger), - }), -}).extend(cv.COMPONENT_SCHEMA), validate_min_max_value) +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema(UNIT_STEPS, ICON_ROTATE_RIGHT, 0, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(RotaryEncoderSensor), + cv.Required(CONF_PIN_A): cv.All( + pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt + ), + cv.Required(CONF_PIN_B): cv.All( + pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt + ), + cv.Optional(CONF_PIN_RESET): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_RESOLUTION, default=1): cv.enum(RESOLUTIONS, int=True), + cv.Optional(CONF_MIN_VALUE): cv.int_, + cv.Optional(CONF_MAX_VALUE): cv.int_, + cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + RotaryEncoderClockwiseTrigger + ), + } + ), + cv.Optional(CONF_ON_ANTICLOCKWISE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + RotaryEncoderAnticlockwiseTrigger + ), + } + ), + } + ) + .extend(cv.COMPONENT_SCHEMA), + validate_min_max_value, +) def to_code(config): @@ -82,11 +117,16 @@ def to_code(config): yield automation.build_automation(trigger, [], conf) -@automation.register_action('sensor.rotary_encoder.set_value', RotaryEncoderSetValueAction, - cv.Schema({ - cv.Required(CONF_ID): cv.use_id(sensor.Sensor), - cv.Required(CONF_VALUE): cv.templatable(cv.int_), - })) +@automation.register_action( + "sensor.rotary_encoder.set_value", + RotaryEncoderSetValueAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(sensor.Sensor), + cv.Required(CONF_VALUE): cv.templatable(cv.int_), + } + ), +) def sensor_template_publish_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/rtttl/__init__.py b/esphome/components/rtttl/__init__.py index a276f7cb86f0..97b7405c63ee 100644 --- a/esphome/components/rtttl/__init__.py +++ b/esphome/components/rtttl/__init__.py @@ -4,28 +4,33 @@ from esphome.components.output import FloatOutput from esphome.const import CONF_ID, CONF_OUTPUT, CONF_TRIGGER_ID -CODEOWNERS = ['@glmnet'] -CONF_RTTTL = 'rtttl' -CONF_ON_FINISHED_PLAYBACK = 'on_finished_playback' +CODEOWNERS = ["@glmnet"] +CONF_RTTTL = "rtttl" +CONF_ON_FINISHED_PLAYBACK = "on_finished_playback" -rtttl_ns = cg.esphome_ns.namespace('rtttl') +rtttl_ns = cg.esphome_ns.namespace("rtttl") -Rtttl = rtttl_ns .class_('Rtttl', cg.Component) -PlayAction = rtttl_ns.class_('PlayAction', automation.Action) -StopAction = rtttl_ns.class_('StopAction', automation.Action) -FinishedPlaybackTrigger = rtttl_ns.class_('FinishedPlaybackTrigger', - automation.Trigger.template()) -IsPlayingCondition = rtttl_ns.class_('IsPlayingCondition', automation.Condition) +Rtttl = rtttl_ns.class_("Rtttl", cg.Component) +PlayAction = rtttl_ns.class_("PlayAction", automation.Action) +StopAction = rtttl_ns.class_("StopAction", automation.Action) +FinishedPlaybackTrigger = rtttl_ns.class_( + "FinishedPlaybackTrigger", automation.Trigger.template() +) +IsPlayingCondition = rtttl_ns.class_("IsPlayingCondition", automation.Condition) MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(CONF_ID): cv.declare_id(Rtttl), - cv.Required(CONF_OUTPUT): cv.use_id(FloatOutput), - cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FinishedPlaybackTrigger), - }), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(Rtttl), + cv.Required(CONF_OUTPUT): cv.use_id(FloatOutput), + cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FinishedPlaybackTrigger), + } + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -40,10 +45,17 @@ def to_code(config): yield automation.build_automation(trigger, [], conf) -@automation.register_action('rtttl.play', PlayAction, cv.maybe_simple_value({ - cv.GenerateID(CONF_ID): cv.use_id(Rtttl), - cv.Required(CONF_RTTTL): cv.templatable(cv.string) -}, key=CONF_RTTTL)) +@automation.register_action( + "rtttl.play", + PlayAction, + cv.maybe_simple_value( + { + cv.GenerateID(CONF_ID): cv.use_id(Rtttl), + cv.Required(CONF_RTTTL): cv.templatable(cv.string), + }, + key=CONF_RTTTL, + ), +) def rtttl_play_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -52,18 +64,30 @@ def rtttl_play_to_code(config, action_id, template_arg, args): yield var -@automation.register_action('rtttl.stop', StopAction, cv.Schema({ - cv.GenerateID(): cv.use_id(Rtttl), -})) +@automation.register_action( + "rtttl.stop", + StopAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(Rtttl), + } + ), +) def rtttl_stop_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_condition('rtttl.is_playing', IsPlayingCondition, cv.Schema({ - cv.GenerateID(): cv.use_id(Rtttl), -})) +@automation.register_condition( + "rtttl.is_playing", + IsPlayingCondition, + cv.Schema( + { + cv.GenerateID(): cv.use_id(Rtttl), + } + ), +) def rtttl_is_playing_to_code(config, condition_id, template_arg, args): var = cg.new_Pvariable(condition_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/ruuvi_ble/__init__.py b/esphome/components/ruuvi_ble/__init__.py index 05ba008dd0c8..74aec457bbe0 100644 --- a/esphome/components/ruuvi_ble/__init__.py +++ b/esphome/components/ruuvi_ble/__init__.py @@ -3,14 +3,18 @@ from esphome.components import esp32_ble_tracker from esphome.const import CONF_ID -DEPENDENCIES = ['esp32_ble_tracker'] +DEPENDENCIES = ["esp32_ble_tracker"] -ruuvi_ble_ns = cg.esphome_ns.namespace('ruuvi_ble') -RuuviListener = ruuvi_ble_ns.class_('RuuviListener', esp32_ble_tracker.ESPBTDeviceListener) +ruuvi_ble_ns = cg.esphome_ns.namespace("ruuvi_ble") +RuuviListener = ruuvi_ble_ns.class_( + "RuuviListener", esp32_ble_tracker.ESPBTDeviceListener +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(RuuviListener), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(RuuviListener), + } +).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) def to_code(config): diff --git a/esphome/components/ruuvitag/sensor.py b/esphome/components/ruuvitag/sensor.py index 3d9c724e7b7f..2bde7b485c8c 100644 --- a/esphome/components/ruuvitag/sensor.py +++ b/esphome/components/ruuvitag/sensor.py @@ -1,37 +1,92 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - CONF_PRESSURE, CONF_ACCELERATION, CONF_ACCELERATION_X, CONF_ACCELERATION_Y, \ - CONF_ACCELERATION_Z, CONF_BATTERY_VOLTAGE, CONF_TX_POWER, \ - CONF_MEASUREMENT_SEQUENCE_NUMBER, CONF_MOVEMENT_COUNTER, UNIT_CELSIUS, \ - ICON_THERMOMETER, UNIT_PERCENT, UNIT_VOLT, UNIT_HECTOPASCAL, UNIT_G, \ - UNIT_DECIBEL_MILLIWATT, UNIT_EMPTY, ICON_WATER_PERCENT, ICON_BATTERY, \ - ICON_GAUGE, ICON_ACCELERATION, ICON_ACCELERATION_X, ICON_ACCELERATION_Y, \ - ICON_ACCELERATION_Z, ICON_SIGNAL, CONF_ID +from esphome.const import ( + CONF_HUMIDITY, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + CONF_PRESSURE, + CONF_ACCELERATION, + CONF_ACCELERATION_X, + CONF_ACCELERATION_Y, + CONF_ACCELERATION_Z, + CONF_BATTERY_VOLTAGE, + CONF_TX_POWER, + CONF_MEASUREMENT_SEQUENCE_NUMBER, + CONF_MOVEMENT_COUNTER, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, + UNIT_VOLT, + UNIT_HECTOPASCAL, + UNIT_G, + UNIT_DECIBEL_MILLIWATT, + UNIT_EMPTY, + ICON_GAUGE, + ICON_ACCELERATION, + ICON_ACCELERATION_X, + ICON_ACCELERATION_Y, + ICON_ACCELERATION_Z, + CONF_ID, +) -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['ruuvi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["ruuvi_ble"] -ruuvitag_ns = cg.esphome_ns.namespace('ruuvitag') +ruuvitag_ns = cg.esphome_ns.namespace("ruuvitag") RuuviTag = ruuvitag_ns.class_( - 'RuuviTag', esp32_ble_tracker.ESPBTDeviceListener, cg.Component) + "RuuviTag", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(RuuviTag), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 2), - cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 2), - cv.Optional(CONF_ACCELERATION): sensor.sensor_schema(UNIT_G, ICON_ACCELERATION, 3), - cv.Optional(CONF_ACCELERATION_X): sensor.sensor_schema(UNIT_G, ICON_ACCELERATION_X, 3), - cv.Optional(CONF_ACCELERATION_Y): sensor.sensor_schema(UNIT_G, ICON_ACCELERATION_Y, 3), - cv.Optional(CONF_ACCELERATION_Z): sensor.sensor_schema(UNIT_G, ICON_ACCELERATION_Z, 3), - cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_BATTERY, 3), - cv.Optional(CONF_TX_POWER): sensor.sensor_schema(UNIT_DECIBEL_MILLIWATT, ICON_SIGNAL, 0), - cv.Optional(CONF_MOVEMENT_COUNTER): sensor.sensor_schema(UNIT_EMPTY, ICON_GAUGE, 0), - cv.Optional(CONF_MEASUREMENT_SEQUENCE_NUMBER): sensor.sensor_schema(UNIT_EMPTY, ICON_GAUGE, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RuuviTag), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + UNIT_HECTOPASCAL, ICON_EMPTY, 2, DEVICE_CLASS_PRESSURE + ), + cv.Optional(CONF_ACCELERATION): sensor.sensor_schema( + UNIT_G, ICON_ACCELERATION, 3, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_ACCELERATION_X): sensor.sensor_schema( + UNIT_G, ICON_ACCELERATION_X, 3, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_ACCELERATION_Y): sensor.sensor_schema( + UNIT_G, ICON_ACCELERATION_Y, 3, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_ACCELERATION_Z): sensor.sensor_schema( + UNIT_G, ICON_ACCELERATION_Z, 3, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE + ), + cv.Optional(CONF_TX_POWER): sensor.sensor_schema( + UNIT_DECIBEL_MILLIWATT, ICON_EMPTY, 0, DEVICE_CLASS_SIGNAL_STRENGTH + ), + cv.Optional(CONF_MOVEMENT_COUNTER): sensor.sensor_schema( + UNIT_EMPTY, ICON_GAUGE, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_MEASUREMENT_SEQUENCE_NUMBER): sensor.sensor_schema( + UNIT_EMPTY, ICON_GAUGE, 0, DEVICE_CLASS_EMPTY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py index eef6f4439090..aa3e5b65ba9b 100644 --- a/esphome/components/scd30/sensor.py +++ b/esphome/components/scd30/sensor.py @@ -2,38 +2,61 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, UNIT_PARTS_PER_MILLION, \ - CONF_HUMIDITY, CONF_TEMPERATURE, ICON_MOLECULE_CO2, \ - UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT, CONF_CO2 +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_EMPTY, + CONF_HUMIDITY, + CONF_TEMPERATURE, + CONF_CO2, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_PARTS_PER_MILLION, + ICON_MOLECULE_CO2, + UNIT_CELSIUS, + UNIT_PERCENT, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -scd30_ns = cg.esphome_ns.namespace('scd30') -SCD30Component = scd30_ns.class_('SCD30Component', cg.PollingComponent, i2c.I2CDevice) +scd30_ns = cg.esphome_ns.namespace("scd30") +SCD30Component = scd30_ns.class_("SCD30Component", cg.PollingComponent, i2c.I2CDevice) -CONF_AUTOMATIC_SELF_CALIBRATION = 'automatic_self_calibration' -CONF_ALTITUDE_COMPENSATION = 'altitude_compensation' -CONF_AMBIENT_PRESSURE_COMPENSATION = 'ambient_pressure_compensation' -CONF_TEMPERATURE_OFFSET = 'temperature_offset' +CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" +CONF_ALTITUDE_COMPENSATION = "altitude_compensation" +CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" +CONF_TEMPERATURE_OFFSET = "temperature_offset" def remove_altitude_suffix(value): - return re.sub(r"\s*(?:m(?:\s+a\.s\.l)?)|(?:MAM?SL)$", '', value) + return re.sub(r"\s*(?:m(?:\s+a\.s\.l)?)|(?:MAM?SL)$", "", value) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(SCD30Component), - cv.Optional(CONF_CO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, - ICON_MOLECULE_CO2, 0), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), - cv.Optional(CONF_AUTOMATIC_SELF_CALIBRATION, default=True): cv.boolean, - cv.Optional(CONF_ALTITUDE_COMPENSATION): cv.All(remove_altitude_suffix, - cv.int_range(min=0, max=0xFFFF, - max_included=False)), - cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure, - cv.Optional(CONF_TEMPERATURE_OFFSET): cv.temperature, -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x61)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SCD30Component), + cv.Optional(CONF_CO2): sensor.sensor_schema( + UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_AUTOMATIC_SELF_CALIBRATION, default=True): cv.boolean, + cv.Optional(CONF_ALTITUDE_COMPENSATION): cv.All( + remove_altitude_suffix, + cv.int_range(min=0, max=0xFFFF, max_included=False), + ), + cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure, + cv.Optional(CONF_TEMPERATURE_OFFSET): cv.temperature, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x61)) +) def to_code(config): @@ -46,7 +69,11 @@ def to_code(config): cg.add(var.set_altitude_compensation(config[CONF_ALTITUDE_COMPENSATION])) if CONF_AMBIENT_PRESSURE_COMPENSATION in config: - cg.add(var.set_ambient_pressure_compensation(config[CONF_AMBIENT_PRESSURE_COMPENSATION])) + cg.add( + var.set_ambient_pressure_compensation( + config[CONF_AMBIENT_PRESSURE_COMPENSATION] + ) + ) if CONF_TEMPERATURE_OFFSET in config: cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) diff --git a/esphome/components/script/__init__.py b/esphome/components/script/__init__.py index cdb334446ada..45b01778da89 100644 --- a/esphome/components/script/__init__.py +++ b/esphome/components/script/__init__.py @@ -4,23 +4,23 @@ from esphome.automation import maybe_simple_id from esphome.const import CONF_ID, CONF_MODE -CODEOWNERS = ['@esphome/core'] -script_ns = cg.esphome_ns.namespace('script') -Script = script_ns.class_('Script', automation.Trigger.template()) -ScriptExecuteAction = script_ns.class_('ScriptExecuteAction', automation.Action) -ScriptStopAction = script_ns.class_('ScriptStopAction', automation.Action) -ScriptWaitAction = script_ns.class_('ScriptWaitAction', automation.Action, cg.Component) -IsRunningCondition = script_ns.class_('IsRunningCondition', automation.Condition) -SingleScript = script_ns.class_('SingleScript', Script) -RestartScript = script_ns.class_('RestartScript', Script) -QueueingScript = script_ns.class_('QueueingScript', Script, cg.Component) -ParallelScript = script_ns.class_('ParallelScript', Script) - -CONF_SINGLE = 'single' -CONF_RESTART = 'restart' -CONF_QUEUED = 'queued' -CONF_PARALLEL = 'parallel' -CONF_MAX_RUNS = 'max_runs' +CODEOWNERS = ["@esphome/core"] +script_ns = cg.esphome_ns.namespace("script") +Script = script_ns.class_("Script", automation.Trigger.template()) +ScriptExecuteAction = script_ns.class_("ScriptExecuteAction", automation.Action) +ScriptStopAction = script_ns.class_("ScriptStopAction", automation.Action) +ScriptWaitAction = script_ns.class_("ScriptWaitAction", automation.Action, cg.Component) +IsRunningCondition = script_ns.class_("IsRunningCondition", automation.Condition) +SingleScript = script_ns.class_("SingleScript", Script) +RestartScript = script_ns.class_("RestartScript", Script) +QueueingScript = script_ns.class_("QueueingScript", Script, cg.Component) +ParallelScript = script_ns.class_("ParallelScript", Script) + +CONF_SINGLE = "single" +CONF_RESTART = "restart" +CONF_QUEUED = "queued" +CONF_PARALLEL = "parallel" +CONF_MAX_RUNS = "max_runs" SCRIPT_MODES = { CONF_SINGLE: SingleScript, @@ -34,8 +34,10 @@ def check_max_runs(value): if CONF_MAX_RUNS not in value: return value if value[CONF_MODE] not in [CONF_QUEUED, CONF_PARALLEL]: - raise cv.Invalid("The option 'max_runs' is only valid in 'queue' and 'parallel' mode.", - path=[CONF_MAX_RUNS]) + raise cv.Invalid( + "The option 'max_runs' is only valid in 'queue' and 'parallel' mode.", + path=[CONF_MAX_RUNS], + ) return value @@ -45,13 +47,18 @@ def assign_declare_id(value): return value -CONFIG_SCHEMA = automation.validate_automation({ - # Don't declare id as cv.declare_id yet, because the ID type - # dpeends on the mode. Will be checked later with assign_declare_id - cv.Required(CONF_ID): cv.string_strict, - cv.Optional(CONF_MODE, default=CONF_SINGLE): cv.one_of(*SCRIPT_MODES, lower=True), - cv.Optional(CONF_MAX_RUNS): cv.positive_int, -}, extra_validators=cv.All(check_max_runs, assign_declare_id)) +CONFIG_SCHEMA = automation.validate_automation( + { + # Don't declare id as cv.declare_id yet, because the ID type + # dpeends on the mode. Will be checked later with assign_declare_id + cv.Required(CONF_ID): cv.string_strict, + cv.Optional(CONF_MODE, default=CONF_SINGLE): cv.one_of( + *SCRIPT_MODES, lower=True + ), + cv.Optional(CONF_MAX_RUNS): cv.positive_int, + }, + extra_validators=cv.All(check_max_runs, assign_declare_id), +) def to_code(config): @@ -74,25 +81,35 @@ def to_code(config): yield automation.build_automation(trigger, [], conf) -@automation.register_action('script.execute', ScriptExecuteAction, maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(Script), -})) +@automation.register_action( + "script.execute", + ScriptExecuteAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Script), + } + ), +) def script_execute_action_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_action('script.stop', ScriptStopAction, maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(Script) -})) +@automation.register_action( + "script.stop", + ScriptStopAction, + maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}), +) def script_stop_action_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_action('script.wait', ScriptWaitAction, maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(Script) -})) +@automation.register_action( + "script.wait", + ScriptWaitAction, + maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}), +) def script_wait_action_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = yield cg.new_Pvariable(action_id, template_arg, paren) @@ -100,9 +117,11 @@ def script_wait_action_to_code(config, action_id, template_arg, args): yield var -@automation.register_condition('script.is_running', IsRunningCondition, automation.maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(Script) -})) +@automation.register_condition( + "script.is_running", + IsRunningCondition, + automation.maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}), +) def script_is_running_to_code(config, condition_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(condition_id, template_arg, paren) diff --git a/esphome/components/sds011/sensor.py b/esphome/components/sds011/sensor.py index 0f750810a6aa..fcee6fe7d25c 100644 --- a/esphome/components/sds011/sensor.py +++ b/esphome/components/sds011/sensor.py @@ -1,14 +1,21 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, uart -from esphome.const import (CONF_ID, CONF_PM_10_0, CONF_PM_2_5, CONF_RX_ONLY, - CONF_UPDATE_INTERVAL, UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON) +from esphome.const import ( + CONF_ID, + CONF_PM_10_0, + CONF_PM_2_5, + CONF_RX_ONLY, + CONF_UPDATE_INTERVAL, + DEVICE_CLASS_EMPTY, + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, +) -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] -sds011_ns = cg.esphome_ns.namespace('sds011') -SDS011Component = sds011_ns.class_('SDS011Component', uart.UARTDevice, cg.Component) +sds011_ns = cg.esphome_ns.namespace("sds011") +SDS011Component = sds011_ns.class_("SDS011Component", uart.UARTDevice, cg.Component) def validate_sds011_rx_mode(value): @@ -19,22 +26,37 @@ def validate_sds011_rx_mode(value): 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 cv.Invalid("update_interval has no effect in rx_only mode. Please remove it.", - path=['update_interval']) + raise cv.Invalid( + "update_interval has no effect in rx_only mode. Please remove it.", + path=["update_interval"], + ) return value -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(SDS011Component), - - cv.Optional(CONF_PM_2_5): - sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 1), - cv.Optional(CONF_PM_10_0): - 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): cv.positive_time_period_minutes, -}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA), validate_sds011_rx_mode) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SDS011Component), + cv.Optional(CONF_PM_2_5): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 1, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_10_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 1, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_RX_ONLY, default=False): cv.boolean, + cv.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_minutes, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA), + validate_sds011_rx_mode, +) def to_code(config): diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index 812a4aa42d5e..80b67dfa17e4 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -6,12 +6,20 @@ namespace senseair { static const char *TAG = "senseair"; static const uint8_t SENSEAIR_REQUEST_LENGTH = 8; -static const uint8_t SENSEAIR_RESPONSE_LENGTH = 13; -static const uint8_t SENSEAIR_COMMAND_GET_PPM[] = {0xFE, 0x04, 0x00, 0x00, 0x00, 0x04, 0xE5, 0xC6}; +static const uint8_t SENSEAIR_PPM_STATUS_RESPONSE_LENGTH = 13; +static const uint8_t SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH = 7; +static const uint8_t SENSEAIR_CAL_RESULT_RESPONSE_LENGTH = 7; +static const uint8_t SENSEAIR_COMMAND_GET_PPM_STATUS[] = {0xFE, 0x04, 0x00, 0x00, 0x00, 0x04, 0xE5, 0xC6}; +static const uint8_t SENSEAIR_COMMAND_CLEAR_ACK_REGISTER[] = {0xFE, 0x06, 0x00, 0x00, 0x00, 0x00, 0x9D, 0xC5}; +static const uint8_t SENSEAIR_COMMAND_BACKGROUND_CAL[] = {0xFE, 0x06, 0x00, 0x01, 0x7C, 0x06, 0x6C, 0xC7}; +static const uint8_t SENSEAIR_COMMAND_BACKGROUND_CAL_RESULT[] = {0xFE, 0x03, 0x00, 0x00, 0x00, 0x01, 0x90, 0x05}; +static const uint8_t SENSEAIR_COMMAND_ABC_ENABLE[] = {0xFE, 0x06, 0x00, 0x1F, 0x00, 0xB4, 0xAC, 0x74}; // 180 hours +static const uint8_t SENSEAIR_COMMAND_ABC_DISABLE[] = {0xFE, 0x06, 0x00, 0x1F, 0x00, 0x00, 0xAC, 0x03}; +static const uint8_t SENSEAIR_COMMAND_ABC_GET_PERIOD[] = {0xFE, 0x03, 0x00, 0x1F, 0x00, 0x01, 0xA1, 0xC3}; void SenseAirComponent::update() { - uint8_t response[SENSEAIR_RESPONSE_LENGTH]; - if (!this->senseair_write_command_(SENSEAIR_COMMAND_GET_PPM, response)) { + uint8_t response[SENSEAIR_PPM_STATUS_RESPONSE_LENGTH]; + if (!this->senseair_write_command_(SENSEAIR_COMMAND_GET_PPM_STATUS, response, SENSEAIR_PPM_STATUS_RESPONSE_LENGTH)) { ESP_LOGW(TAG, "Reading data from SenseAir failed!"); this->status_set_warning(); return; @@ -69,14 +77,67 @@ uint16_t SenseAirComponent::senseair_checksum_(uint8_t *ptr, uint8_t length) { return crc; } -bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response) { +void SenseAirComponent::background_calibration() { + ESP_LOGD(TAG, "SenseAir Starting background calibration"); + this->senseair_write_command_(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER, nullptr, 0); + this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL, nullptr, 0); +} + +void SenseAirComponent::background_calibration_result() { + ESP_LOGD(TAG, "SenseAir Requesting background calibration result"); + uint8_t response[SENSEAIR_CAL_RESULT_RESPONSE_LENGTH]; + if (!this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL_RESULT, response, + SENSEAIR_CAL_RESULT_RESPONSE_LENGTH)) { + ESP_LOGE(TAG, "Requesting background calibration result from SenseAir failed!"); + return; + } + + if (response[0] != 0xFE || response[1] != 0x03) { + ESP_LOGE(TAG, "Invalid reply from SenseAir! %02x%02x%02x %02x%02x %02x%02x", response[0], response[1], response[2], + response[3], response[4], response[5], response[6]); + return; + } + + ESP_LOGD(TAG, "SenseAir Result=%s (%02x%02x%02x)", response[2] == 2 ? "OK" : "NOT_OK", response[2], response[3], + response[4]); +} + +void SenseAirComponent::abc_enable() { + ESP_LOGD(TAG, "SenseAir Enabling automatic baseline calibration"); + this->senseair_write_command_(SENSEAIR_COMMAND_ABC_ENABLE, nullptr, 0); +} + +void SenseAirComponent::abc_disable() { + ESP_LOGD(TAG, "SenseAir Disabling automatic baseline calibration"); + this->senseair_write_command_(SENSEAIR_COMMAND_ABC_DISABLE, nullptr, 0); +} + +void SenseAirComponent::abc_get_period() { + ESP_LOGD(TAG, "SenseAir Requesting ABC period"); + uint8_t response[SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH]; + if (!this->senseair_write_command_(SENSEAIR_COMMAND_ABC_GET_PERIOD, response, SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH)) { + ESP_LOGE(TAG, "Requesting ABC period from SenseAir failed!"); + return; + } + + if (response[0] != 0xFE || response[1] != 0x03) { + ESP_LOGE(TAG, "Invalid reply from SenseAir! %02x%02x%02x %02x%02x %02x%02x", response[0], response[1], response[2], + response[3], response[4], response[5], response[6]); + return; + } + + const uint16_t hours = (uint16_t(response[3]) << 8) | response[4]; + ESP_LOGD(TAG, "SenseAir Read ABC Period: %u hours", hours); +} + +bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) { this->flush(); this->write_array(command, SENSEAIR_REQUEST_LENGTH); if (response == nullptr) return true; - bool ret = this->read_array(response, SENSEAIR_RESPONSE_LENGTH); + bool ret = this->read_array(response, response_length); this->flush(); return ret; } diff --git a/esphome/components/senseair/senseair.h b/esphome/components/senseair/senseair.h index 23bcf40b5ae9..c03a0848e915 100644 --- a/esphome/components/senseair/senseair.h +++ b/esphome/components/senseair/senseair.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/automation.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/uart/uart.h" @@ -15,12 +16,68 @@ class SenseAirComponent : public PollingComponent, public uart::UARTDevice { void update() override; void dump_config() override; + void background_calibration(); + void background_calibration_result(); + void abc_get_period(); + void abc_enable(); + void abc_disable(); + protected: uint16_t senseair_checksum_(uint8_t *ptr, uint8_t length); - bool senseair_write_command_(const uint8_t *command, uint8_t *response); + bool senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length); sensor::Sensor *co2_sensor_{nullptr}; }; +template class SenseAirBackgroundCalibrationAction : public Action { + public: + SenseAirBackgroundCalibrationAction(SenseAirComponent *senseair) : senseair_(senseair) {} + + void play(Ts... x) override { this->senseair_->background_calibration(); } + + protected: + SenseAirComponent *senseair_; +}; + +template class SenseAirBackgroundCalibrationResultAction : public Action { + public: + SenseAirBackgroundCalibrationResultAction(SenseAirComponent *senseair) : senseair_(senseair) {} + + void play(Ts... x) override { this->senseair_->background_calibration_result(); } + + protected: + SenseAirComponent *senseair_; +}; + +template class SenseAirABCEnableAction : public Action { + public: + SenseAirABCEnableAction(SenseAirComponent *senseair) : senseair_(senseair) {} + + void play(Ts... x) override { this->senseair_->abc_enable(); } + + protected: + SenseAirComponent *senseair_; +}; + +template class SenseAirABCDisableAction : public Action { + public: + SenseAirABCDisableAction(SenseAirComponent *senseair) : senseair_(senseair) {} + + void play(Ts... x) override { this->senseair_->abc_disable(); } + + protected: + SenseAirComponent *senseair_; +}; + +template class SenseAirABCGetPeriodAction : public Action { + public: + SenseAirABCGetPeriodAction(SenseAirComponent *senseair) : senseair_(senseair) {} + + void play(Ts... x) override { this->senseair_->abc_get_period(); } + + protected: + SenseAirComponent *senseair_; +}; + } // namespace senseair } // namespace esphome diff --git a/esphome/components/senseair/sensor.py b/esphome/components/senseair/sensor.py index c01587115690..6f4865156359 100644 --- a/esphome/components/senseair/sensor.py +++ b/esphome/components/senseair/sensor.py @@ -1,17 +1,50 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation +from esphome.automation import maybe_simple_id from esphome.components import sensor, uart -from esphome.const import CONF_CO2, CONF_ID, ICON_MOLECULE_CO2, UNIT_PARTS_PER_MILLION +from esphome.const import ( + CONF_CO2, + CONF_ID, + DEVICE_CLASS_EMPTY, + ICON_MOLECULE_CO2, + UNIT_PARTS_PER_MILLION, +) -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] -senseair_ns = cg.esphome_ns.namespace('senseair') -SenseAirComponent = senseair_ns.class_('SenseAirComponent', cg.PollingComponent, uart.UARTDevice) +senseair_ns = cg.esphome_ns.namespace("senseair") +SenseAirComponent = senseair_ns.class_( + "SenseAirComponent", cg.PollingComponent, uart.UARTDevice +) +SenseAirBackgroundCalibrationAction = senseair_ns.class_( + "SenseAirBackgroundCalibrationAction", automation.Action +) +SenseAirBackgroundCalibrationResultAction = senseair_ns.class_( + "SenseAirBackgroundCalibrationResultAction", automation.Action +) +SenseAirABCEnableAction = senseair_ns.class_( + "SenseAirABCEnableAction", automation.Action +) +SenseAirABCDisableAction = senseair_ns.class_( + "SenseAirABCDisableAction", automation.Action +) +SenseAirABCGetPeriodAction = senseair_ns.class_( + "SenseAirABCGetPeriodAction", automation.Action +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(SenseAirComponent), - cv.Required(CONF_CO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0), -}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SenseAirComponent), + cv.Required(CONF_CO2): sensor.sensor_schema( + UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): @@ -22,3 +55,34 @@ def to_code(config): if CONF_CO2 in config: sens = yield sensor.new_sensor(config[CONF_CO2]) cg.add(var.set_co2_sensor(sens)) + + +CALIBRATION_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(SenseAirComponent), + } +) + + +@automation.register_action( + "senseair.background_calibration", + SenseAirBackgroundCalibrationAction, + CALIBRATION_ACTION_SCHEMA, +) +@automation.register_action( + "senseair.background_calibration_result", + SenseAirBackgroundCalibrationResultAction, + CALIBRATION_ACTION_SCHEMA, +) +@automation.register_action( + "senseair.abc_enable", SenseAirABCEnableAction, CALIBRATION_ACTION_SCHEMA +) +@automation.register_action( + "senseair.abc_disable", SenseAirABCDisableAction, CALIBRATION_ACTION_SCHEMA +) +@automation.register_action( + "senseair.abc_get_period", SenseAirABCGetPeriodAction, CALIBRATION_ACTION_SCHEMA +) +def senseair_action_to_code(config, action_id, template_arg, args): + paren = yield cg.get_variable(config[CONF_ID]) + yield cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 671bbe2b09eb..a10c5d7326d4 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -4,15 +4,66 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt -from esphome.const import CONF_ABOVE, CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, \ - CONF_EXPIRE_AFTER, CONF_FILTERS, CONF_FROM, CONF_ICON, CONF_ID, CONF_INTERNAL, \ - CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, CONF_SEND_EVERY, CONF_SEND_FIRST_AT, \ - CONF_TO, CONF_TRIGGER_ID, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, CONF_NAME, CONF_MQTT_ID, \ - CONF_FORCE_UPDATE +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ABOVE, + CONF_ACCURACY_DECIMALS, + CONF_ALPHA, + CONF_BELOW, + CONF_EXPIRE_AFTER, + CONF_FILTERS, + CONF_FROM, + CONF_ICON, + CONF_ID, + CONF_INTERNAL, + CONF_ON_RAW_VALUE, + CONF_ON_VALUE, + CONF_ON_VALUE_RANGE, + CONF_SEND_EVERY, + CONF_SEND_FIRST_AT, + CONF_TO, + CONF_TRIGGER_ID, + CONF_UNIT_OF_MEASUREMENT, + CONF_WINDOW_SIZE, + CONF_NAME, + CONF_MQTT_ID, + CONF_FORCE_UPDATE, + UNIT_EMPTY, + ICON_EMPTY, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TIMESTAMP, + DEVICE_CLASS_VOLTAGE, +) from esphome.core import CORE, coroutine, coroutine_with_priority from esphome.util import Registry -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] +DEVICE_CLASSES = [ + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TIMESTAMP, + DEVICE_CLASS_VOLTAGE, +] + IS_PLATFORM_COMPONENT = True @@ -20,181 +71,302 @@ def validate_send_first_at(value): send_first_at = value.get(CONF_SEND_FIRST_AT) send_every = value[CONF_SEND_EVERY] if send_first_at is not None and send_first_at > send_every: - raise cv.Invalid("send_first_at must be smaller than or equal to send_every! {} <= {}" - "".format(send_first_at, send_every)) + raise cv.Invalid( + "send_first_at must be smaller than or equal to send_every! {} <= {}" + "".format(send_first_at, send_every) + ) return value FILTER_REGISTRY = Registry() -validate_filters = cv.validate_registry('filter', FILTER_REGISTRY) +validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) def validate_datapoint(value): if isinstance(value, dict): - return cv.Schema({ - cv.Required(CONF_FROM): cv.float_, - cv.Required(CONF_TO): cv.float_, - })(value) + return cv.Schema( + { + cv.Required(CONF_FROM): cv.float_, + cv.Required(CONF_TO): cv.float_, + } + )(value) value = cv.string(value) - if '->' not in value: + if "->" not in value: raise cv.Invalid("Datapoint mapping must contain '->'") - a, b = value.split('->', 1) + a, b = value.split("->", 1) a, b = a.strip(), b.strip() - return validate_datapoint({ - CONF_FROM: cv.float_(a), - CONF_TO: cv.float_(b) - }) + return validate_datapoint({CONF_FROM: cv.float_(a), CONF_TO: cv.float_(b)}) # Base -sensor_ns = cg.esphome_ns.namespace('sensor') -Sensor = sensor_ns.class_('Sensor', cg.Nameable) -SensorPtr = Sensor.operator('ptr') +sensor_ns = cg.esphome_ns.namespace("sensor") +Sensor = sensor_ns.class_("Sensor", cg.Nameable) +SensorPtr = Sensor.operator("ptr") # Triggers -SensorStateTrigger = sensor_ns.class_('SensorStateTrigger', automation.Trigger.template(cg.float_)) -SensorRawStateTrigger = sensor_ns.class_('SensorRawStateTrigger', - automation.Trigger.template(cg.float_)) -ValueRangeTrigger = sensor_ns.class_('ValueRangeTrigger', automation.Trigger.template(cg.float_), - cg.Component) -SensorPublishAction = sensor_ns.class_('SensorPublishAction', automation.Action) +SensorStateTrigger = sensor_ns.class_( + "SensorStateTrigger", automation.Trigger.template(cg.float_) +) +SensorRawStateTrigger = sensor_ns.class_( + "SensorRawStateTrigger", automation.Trigger.template(cg.float_) +) +ValueRangeTrigger = sensor_ns.class_( + "ValueRangeTrigger", automation.Trigger.template(cg.float_), cg.Component +) +SensorPublishAction = sensor_ns.class_("SensorPublishAction", automation.Action) # Filters -Filter = sensor_ns.class_('Filter') -MedianFilter = sensor_ns.class_('MedianFilter', Filter) -SlidingWindowMovingAverageFilter = sensor_ns.class_('SlidingWindowMovingAverageFilter', Filter) -ExponentialMovingAverageFilter = sensor_ns.class_('ExponentialMovingAverageFilter', Filter) -LambdaFilter = sensor_ns.class_('LambdaFilter', Filter) -OffsetFilter = sensor_ns.class_('OffsetFilter', Filter) -MultiplyFilter = sensor_ns.class_('MultiplyFilter', Filter) -FilterOutValueFilter = sensor_ns.class_('FilterOutValueFilter', Filter) -ThrottleFilter = sensor_ns.class_('ThrottleFilter', Filter) -DebounceFilter = sensor_ns.class_('DebounceFilter', Filter, cg.Component) -HeartbeatFilter = sensor_ns.class_('HeartbeatFilter', Filter, cg.Component) -DeltaFilter = sensor_ns.class_('DeltaFilter', Filter) -OrFilter = sensor_ns.class_('OrFilter', Filter) -CalibrateLinearFilter = sensor_ns.class_('CalibrateLinearFilter', Filter) -CalibratePolynomialFilter = sensor_ns.class_('CalibratePolynomialFilter', Filter) -SensorInRangeCondition = sensor_ns.class_('SensorInRangeCondition', Filter) +Filter = sensor_ns.class_("Filter") +MedianFilter = sensor_ns.class_("MedianFilter", Filter) +MinFilter = sensor_ns.class_("MinFilter", Filter) +MaxFilter = sensor_ns.class_("MaxFilter", Filter) +SlidingWindowMovingAverageFilter = sensor_ns.class_( + "SlidingWindowMovingAverageFilter", Filter +) +ExponentialMovingAverageFilter = sensor_ns.class_( + "ExponentialMovingAverageFilter", Filter +) +LambdaFilter = sensor_ns.class_("LambdaFilter", Filter) +OffsetFilter = sensor_ns.class_("OffsetFilter", Filter) +MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter) +FilterOutValueFilter = sensor_ns.class_("FilterOutValueFilter", Filter) +ThrottleFilter = sensor_ns.class_("ThrottleFilter", Filter) +DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component) +HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter, cg.Component) +DeltaFilter = sensor_ns.class_("DeltaFilter", Filter) +OrFilter = sensor_ns.class_("OrFilter", Filter) +CalibrateLinearFilter = sensor_ns.class_("CalibrateLinearFilter", Filter) +CalibratePolynomialFilter = sensor_ns.class_("CalibratePolynomialFilter", Filter) +SensorInRangeCondition = sensor_ns.class_("SensorInRangeCondition", Filter) unit_of_measurement = cv.string_strict accuracy_decimals = cv.int_ icon = cv.icon - -SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ - cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTSensorComponent), - cv.GenerateID(): cv.declare_id(Sensor), - cv.Optional(CONF_UNIT_OF_MEASUREMENT): unit_of_measurement, - cv.Optional(CONF_ICON): icon, - cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals, - cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, - cv.Optional(CONF_EXPIRE_AFTER): cv.All(cv.requires_component('mqtt'), - cv.Any(None, cv.positive_time_period_milliseconds)), - cv.Optional(CONF_FILTERS): validate_filters, - cv.Optional(CONF_ON_VALUE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorStateTrigger), - }), - cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorRawStateTrigger), - }), - cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), - cv.Optional(CONF_ABOVE): cv.float_, - cv.Optional(CONF_BELOW): cv.float_, - }, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW)), -}) - - -def sensor_schema(unit_of_measurement_, icon_, accuracy_decimals_): - # type: (str, str, int) -> cv.Schema - return SENSOR_SCHEMA.extend({ - cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=unit_of_measurement_): unit_of_measurement, - cv.Optional(CONF_ICON, default=icon_): icon, - cv.Optional(CONF_ACCURACY_DECIMALS, default=accuracy_decimals_): accuracy_decimals, - }) - - -@FILTER_REGISTRY.register('offset', OffsetFilter, cv.float_) +device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + +SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent), + cv.GenerateID(): cv.declare_id(Sensor), + cv.Optional(CONF_UNIT_OF_MEASUREMENT): unit_of_measurement, + cv.Optional(CONF_ICON): icon, + cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals, + cv.Optional(CONF_DEVICE_CLASS): device_class, + cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, + cv.Optional(CONF_EXPIRE_AFTER): cv.All( + cv.requires_component("mqtt"), + cv.Any(None, cv.positive_time_period_milliseconds), + ), + cv.Optional(CONF_FILTERS): validate_filters, + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorStateTrigger), + } + ), + cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorRawStateTrigger), + } + ), + cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), + cv.Optional(CONF_ABOVE): cv.float_, + cv.Optional(CONF_BELOW): cv.float_, + }, + cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), + ), + } +) + + +def sensor_schema(unit_of_measurement_, icon_, accuracy_decimals_, device_class_): + # type: (str, str, int, str) -> cv.Schema + schema = SENSOR_SCHEMA + if unit_of_measurement_ != UNIT_EMPTY: + schema = schema.extend( + { + cv.Optional( + CONF_UNIT_OF_MEASUREMENT, default=unit_of_measurement_ + ): unit_of_measurement + } + ) + if icon_ != ICON_EMPTY: + schema = schema.extend({cv.Optional(CONF_ICON, default=icon_): icon}) + if accuracy_decimals_ != 0: + schema = schema.extend( + { + cv.Optional( + CONF_ACCURACY_DECIMALS, default=accuracy_decimals_ + ): accuracy_decimals, + } + ) + if device_class_ != DEVICE_CLASS_EMPTY: + schema = schema.extend( + {cv.Optional(CONF_DEVICE_CLASS, default=device_class_): device_class} + ) + return schema + + +@FILTER_REGISTRY.register("offset", OffsetFilter, cv.float_) def offset_filter_to_code(config, filter_id): yield cg.new_Pvariable(filter_id, config) -@FILTER_REGISTRY.register('multiply', MultiplyFilter, cv.float_) +@FILTER_REGISTRY.register("multiply", MultiplyFilter, cv.float_) def multiply_filter_to_code(config, filter_id): yield cg.new_Pvariable(filter_id, config) -@FILTER_REGISTRY.register('filter_out', FilterOutValueFilter, cv.float_) +@FILTER_REGISTRY.register("filter_out", FilterOutValueFilter, cv.float_) def filter_out_filter_to_code(config, filter_id): yield cg.new_Pvariable(filter_id, config) -MEDIAN_SCHEMA = cv.All(cv.Schema({ - cv.Optional(CONF_WINDOW_SIZE, default=5): cv.positive_not_null_int, - cv.Optional(CONF_SEND_EVERY, default=5): cv.positive_not_null_int, - cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int, -}), validate_send_first_at) +MEDIAN_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_WINDOW_SIZE, default=5): cv.positive_not_null_int, + cv.Optional(CONF_SEND_EVERY, default=5): cv.positive_not_null_int, + cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int, + } + ), + validate_send_first_at, +) -@FILTER_REGISTRY.register('median', MedianFilter, MEDIAN_SCHEMA) +@FILTER_REGISTRY.register("median", MedianFilter, MEDIAN_SCHEMA) def median_filter_to_code(config, filter_id): - yield cg.new_Pvariable(filter_id, config[CONF_WINDOW_SIZE], config[CONF_SEND_EVERY], - config[CONF_SEND_FIRST_AT]) - - -SLIDING_AVERAGE_SCHEMA = cv.All(cv.Schema({ - cv.Optional(CONF_WINDOW_SIZE, default=15): cv.positive_not_null_int, - cv.Optional(CONF_SEND_EVERY, default=15): cv.positive_not_null_int, - cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int, -}), validate_send_first_at) - - -@FILTER_REGISTRY.register('sliding_window_moving_average', SlidingWindowMovingAverageFilter, - SLIDING_AVERAGE_SCHEMA) + yield cg.new_Pvariable( + filter_id, + config[CONF_WINDOW_SIZE], + config[CONF_SEND_EVERY], + config[CONF_SEND_FIRST_AT], + ) + + +MIN_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_WINDOW_SIZE, default=5): cv.positive_not_null_int, + cv.Optional(CONF_SEND_EVERY, default=5): cv.positive_not_null_int, + cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int, + } + ), + validate_send_first_at, +) + + +@FILTER_REGISTRY.register("min", MinFilter, MIN_SCHEMA) +def min_filter_to_code(config, filter_id): + yield cg.new_Pvariable( + filter_id, + config[CONF_WINDOW_SIZE], + config[CONF_SEND_EVERY], + config[CONF_SEND_FIRST_AT], + ) + + +MAX_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_WINDOW_SIZE, default=5): cv.positive_not_null_int, + cv.Optional(CONF_SEND_EVERY, default=5): cv.positive_not_null_int, + cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int, + } + ), + validate_send_first_at, +) + + +@FILTER_REGISTRY.register("max", MaxFilter, MAX_SCHEMA) +def max_filter_to_code(config, filter_id): + yield cg.new_Pvariable( + filter_id, + config[CONF_WINDOW_SIZE], + config[CONF_SEND_EVERY], + config[CONF_SEND_FIRST_AT], + ) + + +SLIDING_AVERAGE_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_WINDOW_SIZE, default=15): cv.positive_not_null_int, + cv.Optional(CONF_SEND_EVERY, default=15): cv.positive_not_null_int, + cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int, + } + ), + validate_send_first_at, +) + + +@FILTER_REGISTRY.register( + "sliding_window_moving_average", + SlidingWindowMovingAverageFilter, + SLIDING_AVERAGE_SCHEMA, +) def sliding_window_moving_average_filter_to_code(config, filter_id): - yield cg.new_Pvariable(filter_id, config[CONF_WINDOW_SIZE], config[CONF_SEND_EVERY], - config[CONF_SEND_FIRST_AT]) - - -@FILTER_REGISTRY.register('exponential_moving_average', ExponentialMovingAverageFilter, cv.Schema({ - cv.Optional(CONF_ALPHA, default=0.1): cv.positive_float, - cv.Optional(CONF_SEND_EVERY, default=15): cv.positive_not_null_int, -})) + yield cg.new_Pvariable( + filter_id, + config[CONF_WINDOW_SIZE], + config[CONF_SEND_EVERY], + config[CONF_SEND_FIRST_AT], + ) + + +@FILTER_REGISTRY.register( + "exponential_moving_average", + ExponentialMovingAverageFilter, + cv.Schema( + { + cv.Optional(CONF_ALPHA, default=0.1): cv.positive_float, + cv.Optional(CONF_SEND_EVERY, default=15): cv.positive_not_null_int, + } + ), +) 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.returning_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)) + lambda_ = yield cg.process_lambda( + config, [(float, "x")], return_type=cg.optional.template(float) + ) yield cg.new_Pvariable(filter_id, lambda_) -@FILTER_REGISTRY.register('delta', DeltaFilter, cv.float_) +@FILTER_REGISTRY.register("delta", DeltaFilter, cv.float_) def delta_filter_to_code(config, filter_id): yield cg.new_Pvariable(filter_id, config) -@FILTER_REGISTRY.register('or', OrFilter, validate_filters) +@FILTER_REGISTRY.register("or", OrFilter, validate_filters) def or_filter_to_code(config, filter_id): filters = yield build_filters(config) yield cg.new_Pvariable(filter_id, filters) -@FILTER_REGISTRY.register('throttle', ThrottleFilter, cv.positive_time_period_milliseconds) +@FILTER_REGISTRY.register( + "throttle", ThrottleFilter, cv.positive_time_period_milliseconds +) def throttle_filter_to_code(config, filter_id): yield cg.new_Pvariable(filter_id, config) -@FILTER_REGISTRY.register('heartbeat', HeartbeatFilter, cv.positive_time_period_milliseconds) +@FILTER_REGISTRY.register( + "heartbeat", HeartbeatFilter, cv.positive_time_period_milliseconds +) def heartbeat_filter_to_code(config, filter_id): var = cg.new_Pvariable(filter_id, config) yield cg.register_component(var, {}) yield var -@FILTER_REGISTRY.register('debounce', DebounceFilter, cv.positive_time_period_milliseconds) +@FILTER_REGISTRY.register( + "debounce", DebounceFilter, cv.positive_time_period_milliseconds +) def debounce_filter_to_code(config, filter_id): var = cg.new_Pvariable(filter_id, config) yield cg.register_component(var, {}) @@ -203,13 +375,20 @@ def debounce_filter_to_code(config, filter_id): def validate_not_all_from_same(config): if all(conf[CONF_FROM] == config[0][CONF_FROM] for conf in config): - raise cv.Invalid("The 'from' values of the calibrate_linear filter cannot all point " - "to the same value! Please add more values to the filter.") + raise cv.Invalid( + "The 'from' values of the calibrate_linear filter cannot all point " + "to the same value! Please add more values to the filter." + ) return config -@FILTER_REGISTRY.register('calibrate_linear', CalibrateLinearFilter, cv.All( - cv.ensure_list(validate_datapoint), cv.Length(min=2), validate_not_all_from_same)) +@FILTER_REGISTRY.register( + "calibrate_linear", + CalibrateLinearFilter, + cv.All( + cv.ensure_list(validate_datapoint), cv.Length(min=2), validate_not_all_from_same + ), +) def calibrate_linear_filter_to_code(config, filter_id): x = [conf[CONF_FROM] for conf in config] y = [conf[CONF_TO] for conf in config] @@ -217,26 +396,40 @@ def calibrate_linear_filter_to_code(config, filter_id): yield cg.new_Pvariable(filter_id, k, b) -CONF_DATAPOINTS = 'datapoints' -CONF_DEGREE = 'degree' +CONF_DATAPOINTS = "datapoints" +CONF_DEGREE = "degree" def validate_calibrate_polynomial(config): if config[CONF_DEGREE] >= len(config[CONF_DATAPOINTS]): - raise cv.Invalid("Degree is too high! Maximum possible degree with given datapoints is " - "{}".format(len(config[CONF_DATAPOINTS]) - 1), [CONF_DEGREE]) + raise cv.Invalid( + "Degree is too high! Maximum possible degree with given datapoints is " + "{}".format(len(config[CONF_DATAPOINTS]) - 1), + [CONF_DEGREE], + ) return config -@FILTER_REGISTRY.register('calibrate_polynomial', CalibratePolynomialFilter, cv.All(cv.Schema({ - cv.Required(CONF_DATAPOINTS): cv.All(cv.ensure_list(validate_datapoint), cv.Length(min=1)), - cv.Required(CONF_DEGREE): cv.positive_int, -}), validate_calibrate_polynomial)) +@FILTER_REGISTRY.register( + "calibrate_polynomial", + CalibratePolynomialFilter, + cv.All( + cv.Schema( + { + cv.Required(CONF_DATAPOINTS): cv.All( + cv.ensure_list(validate_datapoint), cv.Length(min=1) + ), + cv.Required(CONF_DEGREE): cv.positive_int, + } + ), + validate_calibrate_polynomial, + ), +) def calibrate_polynomial_filter_to_code(config, filter_id): x = [conf[CONF_FROM] for conf in config[CONF_DATAPOINTS]] y = [conf[CONF_TO] for conf in config[CONF_DATAPOINTS]] degree = config[CONF_DEGREE] - a = [[1] + [x_**(i+1) for i in range(degree)] for x_ in x] + a = [[1] + [x_ ** (i + 1) for i in range(degree)] for x_ in x] # Column vector b = [[v] for v in y] res = [v[0] for v in _lstsq(a, b)] @@ -253,6 +446,8 @@ def setup_sensor_core_(var, config): cg.add(var.set_name(config[CONF_NAME])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) + if CONF_DEVICE_CLASS in config: + cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) if CONF_UNIT_OF_MEASUREMENT in config: cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) if CONF_ICON in config: @@ -266,20 +461,20 @@ def setup_sensor_core_(var, config): for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - yield automation.build_automation(trigger, [(float, 'x')], conf) + yield automation.build_automation(trigger, [(float, "x")], conf) for conf in config.get(CONF_ON_RAW_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - yield automation.build_automation(trigger, [(float, 'x')], conf) + yield automation.build_automation(trigger, [(float, "x")], conf) for conf in config.get(CONF_ON_VALUE_RANGE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) yield cg.register_component(trigger, conf) if CONF_ABOVE in conf: - template_ = yield cg.templatable(conf[CONF_ABOVE], [(float, 'x')], float) + template_ = yield cg.templatable(conf[CONF_ABOVE], [(float, "x")], float) cg.add(trigger.set_min(template_)) if CONF_BELOW in conf: - template_ = yield cg.templatable(conf[CONF_BELOW], [(float, 'x')], float) + template_ = yield cg.templatable(conf[CONF_BELOW], [(float, "x")], float) cg.add(trigger.set_max(template_)) - yield automation.build_automation(trigger, [(float, 'x')], conf) + yield automation.build_automation(trigger, [(float, "x")], conf) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) @@ -307,15 +502,19 @@ def new_sensor(config): yield var -SENSOR_IN_RANGE_CONDITION_SCHEMA = cv.All({ - cv.Required(CONF_ID): cv.use_id(Sensor), - cv.Optional(CONF_ABOVE): cv.float_, - cv.Optional(CONF_BELOW): cv.float_, -}, cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW)) +SENSOR_IN_RANGE_CONDITION_SCHEMA = cv.All( + { + cv.Required(CONF_ID): cv.use_id(Sensor), + cv.Optional(CONF_ABOVE): cv.float_, + cv.Optional(CONF_BELOW): cv.float_, + }, + cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), +) -@automation.register_condition('sensor.in_range', SensorInRangeCondition, - SENSOR_IN_RANGE_CONDITION_SCHEMA) +@automation.register_condition( + "sensor.in_range", SensorInRangeCondition, SENSOR_IN_RANGE_CONDITION_SCHEMA +) def sensor_in_range_to_code(config, condition_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(condition_id, template_arg, paren) @@ -367,7 +566,7 @@ def _mat_identity(n): def _mat_dot(a, b): b_t = _mat_transpose(b) - return [[sum(x*y for x, y in zip(row_a, col_b)) for col_b in b_t] for row_a in a] + return [[sum(x * y for x, y in zip(row_a, col_b)) for col_b in b_t] for row_a in a] def _mat_inverse(m): @@ -378,7 +577,7 @@ def _mat_inverse(m): for diag in range(n): # If diag element is 0, swap rows if m[diag][diag] == 0: - for i in range(diag+1, n): + for i in range(diag + 1, n): if m[i][diag] != 0: break else: @@ -415,5 +614,5 @@ def _lstsq(a, b): @coroutine_with_priority(40.0) def to_code(config): - cg.add_define('USE_SENSOR') + cg.add_define("USE_SENSOR") cg.add_global(sensor_ns.using) diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index f7a5b5d7ad61..6dfb11b9c996 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -77,6 +77,68 @@ optional MedianFilter::new_value(float value) { uint32_t MedianFilter::expected_interval(uint32_t input) { return input * this->send_every_; } +// MinFilter +MinFilter::MinFilter(size_t window_size, size_t send_every, size_t send_first_at) + : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} +void MinFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } +void MinFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } +optional MinFilter::new_value(float value) { + if (!isnan(value)) { + while (this->queue_.size() >= this->window_size_) { + this->queue_.pop_front(); + } + this->queue_.push_back(value); + ESP_LOGVV(TAG, "MinFilter(%p)::new_value(%f)", this, value); + } + + if (++this->send_at_ >= this->send_every_) { + this->send_at_ = 0; + + float min = 0.0f; + if (!this->queue_.empty()) { + std::deque::iterator it = std::min_element(queue_.begin(), queue_.end()); + min = *it; + } + + ESP_LOGVV(TAG, "MinFilter(%p)::new_value(%f) SENDING", this, min); + return min; + } + return {}; +} + +uint32_t MinFilter::expected_interval(uint32_t input) { return input * this->send_every_; } + +// MaxFilter +MaxFilter::MaxFilter(size_t window_size, size_t send_every, size_t send_first_at) + : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} +void MaxFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } +void MaxFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } +optional MaxFilter::new_value(float value) { + if (!isnan(value)) { + while (this->queue_.size() >= this->window_size_) { + this->queue_.pop_front(); + } + this->queue_.push_back(value); + ESP_LOGVV(TAG, "MaxFilter(%p)::new_value(%f)", this, value); + } + + if (++this->send_at_ >= this->send_every_) { + this->send_at_ = 0; + + float max = 0.0f; + if (!this->queue_.empty()) { + std::deque::iterator it = std::max_element(queue_.begin(), queue_.end()); + max = *it; + } + + ESP_LOGVV(TAG, "MaxFilter(%p)::new_value(%f) SENDING", this, max); + return max; + } + return {}; +} + +uint32_t MaxFilter::expected_interval(uint32_t input) { return input * this->send_every_; } + // SlidingWindowMovingAverageFilter SlidingWindowMovingAverageFilter::SlidingWindowMovingAverageFilter(size_t window_size, size_t send_every, size_t send_first_at) diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 4c61d4c0a2d1..651d2a8986eb 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -76,6 +76,66 @@ class MedianFilter : public Filter { size_t window_size_; }; +/** Simple min filter. + * + * Takes the min of the last values and pushes it out every . + */ +class MinFilter : public Filter { + public: + /** Construct a MinFilter. + * + * @param window_size The number of values that the min should be returned from. + * @param send_every After how many sensor values should a new one be pushed out. + * @param send_first_at After how many values to forward the very first value. Defaults to the first value + * on startup being published on the first *raw* value, so with no filter applied. Must be less than or equal to + * send_every. + */ + explicit MinFilter(size_t window_size, size_t send_every, size_t send_first_at); + + optional new_value(float value) override; + + void set_send_every(size_t send_every); + void set_window_size(size_t window_size); + + uint32_t expected_interval(uint32_t input) override; + + protected: + std::deque queue_; + size_t send_every_; + size_t send_at_; + size_t window_size_; +}; + +/** Simple max filter. + * + * Takes the max of the last values and pushes it out every . + */ +class MaxFilter : public Filter { + public: + /** Construct a MaxFilter. + * + * @param window_size The number of values that the max should be returned from. + * @param send_every After how many sensor values should a new one be pushed out. + * @param send_first_at After how many values to forward the very first value. Defaults to the first value + * on startup being published on the first *raw* value, so with no filter applied. Must be less than or equal to + * send_every. + */ + explicit MaxFilter(size_t window_size, size_t send_every, size_t send_first_at); + + optional new_value(float value) override; + + void set_send_every(size_t send_every); + void set_window_size(size_t window_size); + + uint32_t expected_interval(uint32_t input) override; + + protected: + std::deque queue_; + size_t send_every_; + size_t send_at_; + size_t window_size_; +}; + /** Simple sliding window moving average filter. * * Essentially just takes takes the average of the last window_size values and pushes them out diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index e12e55e320e4..069a5c592326 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -40,6 +40,13 @@ std::string Sensor::get_icon() { return *this->icon_; return this->icon(); } +void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } +std::string Sensor::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return this->device_class(); +} +std::string Sensor::device_class() { return ""; } std::string Sensor::get_unit_of_measurement() { if (this->unit_of_measurement_.has_value()) return *this->unit_of_measurement_; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index f23f0227676a..6bb6c876ab2b 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -10,6 +10,9 @@ namespace sensor { #define LOG_SENSOR(prefix, type, obj) \ if (obj != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \ + if (!obj->get_device_class().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str()); \ + } \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, obj->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, obj->get_accuracy_decimals()); \ if (!obj->get_icon().empty()) { \ @@ -122,6 +125,12 @@ class Sensor : public Nameable { */ float state; + /// Manually set the Home Assistant device class (see sensor::device_class) + void set_device_class(const std::string &device_class); + + /// Get the device class for this sensor, using the manual override if specified. + std::string get_device_class(); + /** This member variable stores the current raw state of the sensor. Unlike .state, * this will be updated immediately when publish_state is called. */ @@ -130,6 +139,14 @@ class Sensor : public Nameable { /// Return whether this sensor has gotten a full state (that passed through all filters) yet. bool has_state() const; + /** Override this to set the Home Assistant device class for this sensor. + * + * Return "" to disable this feature. + * + * @return The device class of this sensor, for example "temperature". + */ + virtual std::string device_class(); + /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * @@ -174,6 +191,8 @@ class Sensor : public Nameable { /// Return the accuracy in decimals for this sensor. virtual int8_t accuracy_decimals(); // NOLINT + optional device_class_{}; ///< Stores the override of the device class + uint32_t hash_base() override; CallbackManager raw_callback_; ///< Storage for raw state callbacks. diff --git a/esphome/components/servo/__init__.py b/esphome/components/servo/__init__.py index 76690dbcf32e..489c29525564 100644 --- a/esphome/components/servo/__init__.py +++ b/esphome/components/servo/__init__.py @@ -3,26 +3,40 @@ from esphome import automation from esphome.automation import maybe_simple_id from esphome.components.output import FloatOutput -from esphome.const import CONF_ID, CONF_IDLE_LEVEL, CONF_MAX_LEVEL, CONF_MIN_LEVEL, CONF_OUTPUT, \ - CONF_LEVEL, CONF_RESTORE, CONF_TRANSITION_LENGTH +from esphome.const import ( + CONF_ID, + CONF_IDLE_LEVEL, + CONF_MAX_LEVEL, + CONF_MIN_LEVEL, + CONF_OUTPUT, + CONF_LEVEL, + CONF_RESTORE, + CONF_TRANSITION_LENGTH, +) -servo_ns = cg.esphome_ns.namespace('servo') -Servo = servo_ns.class_('Servo', cg.Component) -ServoWriteAction = servo_ns.class_('ServoWriteAction', automation.Action) -ServoDetachAction = servo_ns.class_('ServoDetachAction', automation.Action) +servo_ns = cg.esphome_ns.namespace("servo") +Servo = servo_ns.class_("Servo", cg.Component) +ServoWriteAction = servo_ns.class_("ServoWriteAction", automation.Action) +ServoDetachAction = servo_ns.class_("ServoDetachAction", automation.Action) -CONF_AUTO_DETACH_TIME = 'auto_detach_time' +CONF_AUTO_DETACH_TIME = "auto_detach_time" MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(Servo), - cv.Required(CONF_OUTPUT): cv.use_id(FloatOutput), - cv.Optional(CONF_MIN_LEVEL, default='3%'): cv.percentage, - cv.Optional(CONF_IDLE_LEVEL, default='7.5%'): cv.percentage, - cv.Optional(CONF_MAX_LEVEL, default='12%'): cv.percentage, - cv.Optional(CONF_RESTORE, default=False): cv.boolean, - cv.Optional(CONF_AUTO_DETACH_TIME, default='0s'): cv.positive_time_period_milliseconds, - cv.Optional(CONF_TRANSITION_LENGTH, default='0s'): cv.positive_time_period_milliseconds -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(Servo), + cv.Required(CONF_OUTPUT): cv.use_id(FloatOutput), + cv.Optional(CONF_MIN_LEVEL, default="3%"): cv.percentage, + cv.Optional(CONF_IDLE_LEVEL, default="7.5%"): cv.percentage, + cv.Optional(CONF_MAX_LEVEL, default="12%"): cv.percentage, + cv.Optional(CONF_RESTORE, default=False): cv.boolean, + cv.Optional( + CONF_AUTO_DETACH_TIME, default="0s" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_TRANSITION_LENGTH, default="0s" + ): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -39,10 +53,16 @@ def to_code(config): cg.add(var.set_transition_length(config[CONF_TRANSITION_LENGTH])) -@automation.register_action('servo.write', ServoWriteAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(Servo), - cv.Required(CONF_LEVEL): cv.templatable(cv.possibly_negative_percentage), -})) +@automation.register_action( + "servo.write", + ServoWriteAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Servo), + cv.Required(CONF_LEVEL): cv.templatable(cv.possibly_negative_percentage), + } + ), +) def servo_write_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -51,9 +71,15 @@ def servo_write_to_code(config, action_id, template_arg, args): yield var -@automation.register_action('servo.detach', ServoDetachAction, maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(Servo), -})) +@automation.register_action( + "servo.detach", + ServoDetachAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Servo), + } + ), +) def servo_detach_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/servo/servo.cpp b/esphome/components/servo/servo.cpp index 5d2a9ee23617..6935c34653f9 100644 --- a/esphome/components/servo/servo.cpp +++ b/esphome/components/servo/servo.cpp @@ -31,7 +31,7 @@ void Servo::loop() { if (this->transition_length_) { float new_value; float travel_diff = this->target_value_ - this->source_value_; - uint32_t target_runtime = target_runtime = abs((int) ((travel_diff) * this->transition_length_ * 1.0f / 2.0f)); + uint32_t target_runtime = abs((int) ((travel_diff) * this->transition_length_ * 1.0f / 2.0f)); uint32_t current_runtime = millis() - this->start_millis_; float percentage_run = current_runtime * 1.0f / target_runtime * 1.0f; if (percentage_run > 1.0f) { diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index a8963fef7b89..31b40840e3f0 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -1,38 +1,57 @@ 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, ICON_MOLECULE_CO2 - -DEPENDENCIES = ['i2c'] - -sgp30_ns = cg.esphome_ns.namespace('sgp30') -SGP30Component = sgp30_ns.class_('SGP30Component', cg.PollingComponent, i2c.I2CDevice) - -CONF_ECO2 = 'eco2' -CONF_TVOC = 'tvoc' -CONF_BASELINE = 'baseline' -CONF_ECO2_BASELINE = 'eco2_baseline' -CONF_TVOC_BASELINE = 'tvoc_baseline' -CONF_UPTIME = 'uptime' -CONF_COMPENSATION = 'compensation' -CONF_HUMIDITY_SOURCE = 'humidity_source' -CONF_TEMPERATURE_SOURCE = 'temperature_source' - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(SGP30Component), - cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, - ICON_MOLECULE_CO2, 0), - cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0), - cv.Optional(CONF_BASELINE): cv.Schema({ - cv.Required(CONF_ECO2_BASELINE): cv.hex_uint16_t, - cv.Required(CONF_TVOC_BASELINE): cv.hex_uint16_t, - }), - cv.Optional(CONF_COMPENSATION): cv.Schema({ - cv.Required(CONF_HUMIDITY_SOURCE): cv.use_id(sensor.Sensor), - cv.Required(CONF_TEMPERATURE_SOURCE): cv.use_id(sensor.Sensor) - }), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x58)) +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_EMPTY, + ICON_RADIATOR, + UNIT_PARTS_PER_MILLION, + UNIT_PARTS_PER_BILLION, + ICON_MOLECULE_CO2, + CONF_TVOC, +) + +DEPENDENCIES = ["i2c"] + +sgp30_ns = cg.esphome_ns.namespace("sgp30") +SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice) + +CONF_ECO2 = "eco2" +CONF_BASELINE = "baseline" +CONF_ECO2_BASELINE = "eco2_baseline" +CONF_TVOC_BASELINE = "tvoc_baseline" +CONF_UPTIME = "uptime" +CONF_COMPENSATION = "compensation" +CONF_HUMIDITY_SOURCE = "humidity_source" +CONF_TEMPERATURE_SOURCE = "temperature_source" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SGP30Component), + cv.Required(CONF_ECO2): sensor.sensor_schema( + UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY + ), + cv.Required(CONF_TVOC): sensor.sensor_schema( + UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_BASELINE): cv.Schema( + { + cv.Required(CONF_ECO2_BASELINE): cv.hex_uint16_t, + cv.Required(CONF_TVOC_BASELINE): cv.hex_uint16_t, + } + ), + cv.Optional(CONF_COMPENSATION): cv.Schema( + { + cv.Required(CONF_HUMIDITY_SOURCE): cv.use_id(sensor.Sensor), + cv.Required(CONF_TEMPERATURE_SOURCE): cv.use_id(sensor.Sensor), + } + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x58)) +) def to_code(config): diff --git a/esphome/components/sht3xd/sensor.py b/esphome/components/sht3xd/sensor.py index 9bbdc47eecd4..0b30d6780b23 100644 --- a/esphome/components/sht3xd/sensor.py +++ b/esphome/components/sht3xd/sensor.py @@ -1,19 +1,39 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, ICON_WATER_PERCENT, \ - ICON_THERMOMETER, UNIT_CELSIUS, UNIT_PERCENT +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -sht3xd_ns = cg.esphome_ns.namespace('sht3xd') -SHT3XDComponent = sht3xd_ns.class_('SHT3XDComponent', cg.PollingComponent, i2c.I2CDevice) +sht3xd_ns = cg.esphome_ns.namespace("sht3xd") +SHT3XDComponent = sht3xd_ns.class_( + "SHT3XDComponent", cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(SHT3XDComponent), - cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Required(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x44)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SHT3XDComponent), + cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Required(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x44)) +) def to_code(config): diff --git a/esphome/components/shtcx/sensor.py b/esphome/components/shtcx/sensor.py index eb215078e793..4b1c6c7afe63 100644 --- a/esphome/components/shtcx/sensor.py +++ b/esphome/components/shtcx/sensor.py @@ -1,21 +1,39 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, ICON_WATER_PERCENT, \ - ICON_THERMOMETER, UNIT_CELSIUS, UNIT_PERCENT - -DEPENDENCIES = ['i2c'] - -shtcx_ns = cg.esphome_ns.namespace('shtcx') -SHTCXComponent = shtcx_ns.class_('SHTCXComponent', cg.PollingComponent, i2c.I2CDevice) - -SHTCXType = shtcx_ns.enum('SHTCXType') - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(SHTCXComponent), - cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Required(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x70)) +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +DEPENDENCIES = ["i2c"] + +shtcx_ns = cg.esphome_ns.namespace("shtcx") +SHTCXComponent = shtcx_ns.class_("SHTCXComponent", cg.PollingComponent, i2c.I2CDevice) + +SHTCXType = shtcx_ns.enum("SHTCXType") + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SHTCXComponent), + cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Required(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x70)) +) def to_code(config): diff --git a/esphome/components/shutdown/__init__.py b/esphome/components/shutdown/__init__.py index 63db7aee2ecd..f70ffa952087 100644 --- a/esphome/components/shutdown/__init__.py +++ b/esphome/components/shutdown/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] diff --git a/esphome/components/shutdown/switch.py b/esphome/components/shutdown/switch.py index 9826f9bbe53a..e35029afd7a0 100644 --- a/esphome/components/shutdown/switch.py +++ b/esphome/components/shutdown/switch.py @@ -3,16 +3,18 @@ from esphome.components import switch from esphome.const import CONF_ID, CONF_INVERTED, CONF_ICON, ICON_POWER -shutdown_ns = cg.esphome_ns.namespace('shutdown') -ShutdownSwitch = shutdown_ns.class_('ShutdownSwitch', switch.Switch, cg.Component) +shutdown_ns = cg.esphome_ns.namespace("shutdown") +ShutdownSwitch = shutdown_ns.class_("ShutdownSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(ShutdownSwitch), - - cv.Optional(CONF_INVERTED): cv.invalid("Shutdown switches do not support inverted mode!"), - - cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ShutdownSwitch), + cv.Optional(CONF_INVERTED): cv.invalid( + "Shutdown switches do not support inverted mode!" + ), + cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 762e045598bd..3b06ca6d8949 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -4,30 +4,42 @@ from esphome.const import CONF_ID, CONF_TRIGGER_ID from esphome.components import uart -DEPENDENCIES = ['uart'] -CODEOWNERS = ['@glmnet'] +DEPENDENCIES = ["uart"] +CODEOWNERS = ["@glmnet"] MULTI_CONF = True -sim800l_ns = cg.esphome_ns.namespace('sim800l') -Sim800LComponent = sim800l_ns.class_('Sim800LComponent', cg.Component) +sim800l_ns = cg.esphome_ns.namespace("sim800l") +Sim800LComponent = sim800l_ns.class_("Sim800LComponent", cg.Component) -Sim800LReceivedMessageTrigger = sim800l_ns.class_('Sim800LReceivedMessageTrigger', - automation.Trigger.template(cg.std_string, - cg.std_string)) +Sim800LReceivedMessageTrigger = sim800l_ns.class_( + "Sim800LReceivedMessageTrigger", + automation.Trigger.template(cg.std_string, cg.std_string), +) # Actions -Sim800LSendSmsAction = sim800l_ns.class_('Sim800LSendSmsAction', automation.Action) +Sim800LSendSmsAction = sim800l_ns.class_("Sim800LSendSmsAction", automation.Action) +Sim800LDialAction = sim800l_ns.class_("Sim800LDialAction", automation.Action) -CONF_ON_SMS_RECEIVED = 'on_sms_received' -CONF_RECIPIENT = 'recipient' -CONF_MESSAGE = 'message' +CONF_ON_SMS_RECEIVED = "on_sms_received" +CONF_RECIPIENT = "recipient" +CONF_MESSAGE = "message" -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(Sim800LComponent), - cv.Optional(CONF_ON_SMS_RECEIVED): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Sim800LReceivedMessageTrigger), - }), -}).extend(cv.polling_component_schema('5s')).extend(uart.UART_DEVICE_SCHEMA)) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Sim800LComponent), + cv.Optional(CONF_ON_SMS_RECEIVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + Sim800LReceivedMessageTrigger + ), + } + ), + } + ) + .extend(cv.polling_component_schema("5s")) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): @@ -37,18 +49,23 @@ def to_code(config): for conf in config.get(CONF_ON_SMS_RECEIVED, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - yield automation.build_automation(trigger, [(cg.std_string, 'message'), - (cg.std_string, 'sender')], conf) + yield automation.build_automation( + trigger, [(cg.std_string, "message"), (cg.std_string, "sender")], conf + ) -SIM800L_SEND_SMS_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.use_id(Sim800LComponent), - cv.Required(CONF_RECIPIENT): cv.templatable(cv.string_strict), - cv.Required(CONF_MESSAGE): cv.templatable(cv.string), -}) +SIM800L_SEND_SMS_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(Sim800LComponent), + cv.Required(CONF_RECIPIENT): cv.templatable(cv.string_strict), + cv.Required(CONF_MESSAGE): cv.templatable(cv.string), + } +) -@automation.register_action('sim800l.send_sms', Sim800LSendSmsAction, SIM800L_SEND_SMS_SCHEMA) +@automation.register_action( + "sim800l.send_sms", Sim800LSendSmsAction, SIM800L_SEND_SMS_SCHEMA +) def sim800l_send_sms_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -57,3 +74,20 @@ def sim800l_send_sms_to_code(config, action_id, template_arg, args): template_ = yield cg.templatable(config[CONF_MESSAGE], args, cg.std_string) cg.add(var.set_message(template_)) yield var + + +SIM800L_DIAL_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(Sim800LComponent), + cv.Required(CONF_RECIPIENT): cv.templatable(cv.string_strict), + } +) + + +@automation.register_action("sim800l.dial", Sim800LDialAction, SIM800L_DIAL_SCHEMA) +def sim800l_dial_to_code(config, action_id, template_arg, args): + paren = yield cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = yield cg.templatable(config[CONF_RECIPIENT], args, cg.std_string) + cg.add(var.set_recipient(template_)) + yield var diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index 9f8c733fa909..ac4c8fc2d289 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -20,6 +20,9 @@ void Sim800LComponent::update() { if (this->registered_ && this->send_pending_) { this->send_cmd_("AT+CSCS=\"GSM\""); this->state_ = STATE_SENDINGSMS1; + } else if (this->registered_ && this->dial_pending_) { + this->send_cmd_("AT+CSCS=\"GSM\""); + this->state_ = STATE_DIALING1; } else { this->send_cmd_("AT"); this->state_ = STATE_CHECK_AT; @@ -212,6 +215,23 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->expect_ack_ = true; } break; + case STATE_DIALING1: + this->send_cmd_("ATD" + this->recipient_ + ';'); + this->state_ = STATE_DIALING2; + break; + case STATE_DIALING2: + if (message == "OK") { + // Dialing + ESP_LOGD(TAG, "Dialing: '%s'", this->recipient_.c_str()); + this->state_ = STATE_INIT; + this->dial_pending_ = false; + } else { + this->registered_ = false; + this->state_ = STATE_INIT; + this->send_cmd_("AT+CMEE=2"); + this->write(26); + } + break; default: ESP_LOGD(TAG, "Unhandled: %s - %d", message.c_str(), this->state_); break; @@ -259,6 +279,12 @@ void Sim800LComponent::dump_config() { ESP_LOGCONFIG(TAG, "SIM800L:"); ESP_LOGCONFIG(TAG, " RSSI: %d dB", this->rssi_); } +void Sim800LComponent::dial(std::string recipient) { + ESP_LOGD(TAG, "Dialing %s", recipient.c_str()); + this->recipient_ = recipient; + this->dial_pending_ = true; + this->update(); +} } // namespace sim800l } // namespace esphome diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index 696eb8890f7e..f8ccf88977a2 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -29,7 +29,9 @@ enum State { STATE_RECEIVEDSMS, STATE_DELETEDSMS, STATE_DISABLE_ECHO, - STATE_PARSE_SMS_OK + STATE_PARSE_SMS_OK, + STATE_DIALING1, + STATE_DIALING2 }; class Sim800LComponent : public uart::UARTDevice, public PollingComponent { @@ -42,6 +44,7 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { this->callback_.add(std::move(callback)); } void send_sms(std::string recipient, std::string message); + void dial(std::string recipient); protected: void send_cmd_(std::string); @@ -60,6 +63,7 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { std::string recipient_; std::string outgoing_message_; bool send_pending_; + bool dial_pending_; CallbackManager callback_; }; @@ -88,5 +92,19 @@ template class Sim800LSendSmsAction : public Action { Sim800LComponent *parent_; }; +template class Sim800LDialAction : public Action { + public: + Sim800LDialAction(Sim800LComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(std::string, recipient) + + void play(Ts... x) { + auto recipient = this->recipient_.value(x...); + this->parent_->dial(recipient); + } + + protected: + Sim800LComponent *parent_; +}; + } // namespace sim800l } // namespace esphome diff --git a/esphome/components/sm16716/__init__.py b/esphome/components/sm16716/__init__.py index 4e342588f977..8030f78f4192 100644 --- a/esphome/components/sm16716/__init__.py +++ b/esphome/components/sm16716/__init__.py @@ -1,21 +1,28 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.const import (CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_ID, - CONF_NUM_CHANNELS, CONF_NUM_CHIPS) +from esphome.const import ( + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_ID, + CONF_NUM_CHANNELS, + CONF_NUM_CHIPS, +) -AUTO_LOAD = ['output'] -sm16716_ns = cg.esphome_ns.namespace('sm16716') -SM16716 = sm16716_ns.class_('SM16716', cg.Component) +AUTO_LOAD = ["output"] +sm16716_ns = cg.esphome_ns.namespace("sm16716") +SM16716 = sm16716_ns.class_("SM16716", cg.Component) MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(SM16716), - cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_NUM_CHANNELS, default=3): cv.int_range(min=3, max=255), - cv.Optional(CONF_NUM_CHIPS, default=1): cv.int_range(min=1, max=85), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SM16716), + cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_NUM_CHANNELS, default=3): cv.int_range(min=3, max=255), + cv.Optional(CONF_NUM_CHIPS, default=1): cv.int_range(min=1, max=85), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/sm16716/output.py b/esphome/components/sm16716/output.py index 93c9ed4ce12b..033bd86e9cbc 100644 --- a/esphome/components/sm16716/output.py +++ b/esphome/components/sm16716/output.py @@ -4,16 +4,18 @@ from esphome.const import CONF_CHANNEL, CONF_ID from . import SM16716 -DEPENDENCIES = ['sm16716'] +DEPENDENCIES = ["sm16716"] -Channel = SM16716.class_('Channel', output.FloatOutput) +Channel = SM16716.class_("Channel", output.FloatOutput) -CONF_SM16716_ID = 'sm16716_id' -CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ - cv.GenerateID(CONF_SM16716_ID): cv.use_id(SM16716), - cv.Required(CONF_ID): cv.declare_id(Channel), - cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535), -}).extend(cv.COMPONENT_SCHEMA) +CONF_SM16716_ID = "sm16716_id" +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.GenerateID(CONF_SM16716_ID): cv.use_id(SM16716), + cv.Required(CONF_ID): cv.declare_id(Channel), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/sm300d2/__init__.py b/esphome/components/sm300d2/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/sm300d2/sensor.py b/esphome/components/sm300d2/sensor.py new file mode 100644 index 000000000000..b1df1fb1b831 --- /dev/null +++ b/esphome/components/sm300d2/sensor.py @@ -0,0 +1,92 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_ID, + CONF_CO2, + CONF_FORMALDEHYDE, + CONF_TVOC, + CONF_PM_2_5, + CONF_PM_10_0, + CONF_TEMPERATURE, + CONF_HUMIDITY, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + UNIT_PARTS_PER_MILLION, + UNIT_MICROGRAMS_PER_CUBIC_METER, + UNIT_CELSIUS, + UNIT_PERCENT, + ICON_EMPTY, + ICON_MOLECULE_CO2, + ICON_FLASK, + ICON_CHEMICAL_WEAPON, + ICON_GRAIN, +) + +DEPENDENCIES = ["uart"] + +sm300d2_ns = cg.esphome_ns.namespace("sm300d2") +SM300D2Sensor = sm300d2_ns.class_("SM300D2Sensor", cg.PollingComponent, uart.UARTDevice) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SM300D2Sensor), + cv.Optional(CONF_CO2): sensor.sensor_schema( + UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_FORMALDEHYDE): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_FLASK, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_TVOC): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 0, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_2_5): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_GRAIN, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PM_10_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_GRAIN, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 0, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .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) + + if CONF_CO2 in config: + sens = yield sensor.new_sensor(config[CONF_CO2]) + cg.add(var.set_co2_sensor(sens)) + if CONF_FORMALDEHYDE in config: + sens = yield sensor.new_sensor(config[CONF_FORMALDEHYDE]) + cg.add(var.set_formaldehyde_sensor(sens)) + if CONF_TVOC in config: + sens = yield sensor.new_sensor(config[CONF_TVOC]) + cg.add(var.set_tvoc_sensor(sens)) + if CONF_PM_2_5 in config: + sens = yield sensor.new_sensor(config[CONF_PM_2_5]) + cg.add(var.set_pm_2_5_sensor(sens)) + if CONF_PM_10_0 in config: + sens = yield sensor.new_sensor(config[CONF_PM_10_0]) + cg.add(var.set_pm_10_0_sensor(sens)) + if CONF_TEMPERATURE in config: + sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) + if CONF_HUMIDITY in config: + sens = yield sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity_sensor(sens)) diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp new file mode 100644 index 000000000000..25abc82f3c53 --- /dev/null +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -0,0 +1,99 @@ +#include "sm300d2.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sm300d2 { + +static const char *TAG = "sm300d2"; +static const uint8_t SM300D2_RESPONSE_LENGTH = 17; + +void SM300D2Sensor::update() { + uint8_t response[SM300D2_RESPONSE_LENGTH]; + + flush(); + bool read_success = read_array(response, SM300D2_RESPONSE_LENGTH); + flush(); + + if (!read_success) { + ESP_LOGW(TAG, "Reading data from SM300D2 failed!"); + status_set_warning(); + return; + } + + if (response[0] != 0x3C || response[1] != 0x02) { + ESP_LOGW(TAG, "Invalid preamble for SM300D2 response!"); + this->status_set_warning(); + return; + } + + uint16_t calculated_checksum = this->sm300d2_checksum_(response); + if (calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) { + ESP_LOGW(TAG, "SM300D2 Checksum doesn't match: 0x%02X!=0x%02X", response[SM300D2_RESPONSE_LENGTH - 1], + calculated_checksum); + this->status_set_warning(); + return; + } + + this->status_clear_warning(); + + ESP_LOGW(TAG, "Successfully read SM300D2 data"); + + const uint16_t co2 = (response[2] * 256) + response[3]; + const uint16_t formaldehyde = (response[4] * 256) + response[5]; + const uint16_t tvoc = (response[6] * 256) + response[7]; + const uint16_t pm_2_5 = (response[8] * 256) + response[9]; + const uint16_t pm_10_0 = (response[10] * 256) + response[11]; + const float temperature = response[12] + (response[13] * 0.1); + const float humidity = response[14] + (response[15] * 0.1); + + ESP_LOGD(TAG, "Received CO₂: %u ppm", co2); + if (this->co2_sensor_ != nullptr) + this->co2_sensor_->publish_state(co2); + + ESP_LOGD(TAG, "Received Formaldehyde: %u µg/m³", formaldehyde); + if (this->formaldehyde_sensor_ != nullptr) + this->formaldehyde_sensor_->publish_state(formaldehyde); + + ESP_LOGD(TAG, "Received TVOC: %u µg/m³", tvoc); + if (this->tvoc_sensor_ != nullptr) + this->tvoc_sensor_->publish_state(tvoc); + + ESP_LOGD(TAG, "Received PM2.5: %u µg/m³", pm_2_5); + if (this->pm_2_5_sensor_ != nullptr) + this->pm_2_5_sensor_->publish_state(pm_2_5); + + ESP_LOGD(TAG, "Received pm_10_0: %u µg/m³", pm_10_0); + if (this->pm_10_0_sensor_ != nullptr) + this->pm_10_0_sensor_->publish_state(pm_10_0); + + ESP_LOGD(TAG, "Received Temperature: %.2f °C", temperature); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + + ESP_LOGD(TAG, "Received Humidity: %.2f percent", humidity); + if (this->humidity_sensor_ != nullptr) + this->humidity_sensor_->publish_state(humidity); +} + +uint16_t SM300D2Sensor::sm300d2_checksum_(uint8_t *ptr) { + uint8_t sum = 0; + for (int i = 0; i < (SM300D2_RESPONSE_LENGTH - 1); i++) { + sum += *ptr++; + } + return sum; +} + +void SM300D2Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "SM300D2:"); + LOG_SENSOR(" ", "CO2", this->co2_sensor_); + LOG_SENSOR(" ", "Formaldehyde", this->formaldehyde_sensor_); + LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); + LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_); + LOG_SENSOR(" ", "PM10", this->pm_10_0_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + this->check_uart_settings(9600); +} + +} // namespace sm300d2 +} // namespace esphome diff --git a/esphome/components/sm300d2/sm300d2.h b/esphome/components/sm300d2/sm300d2.h new file mode 100644 index 000000000000..88c04e9813d2 --- /dev/null +++ b/esphome/components/sm300d2/sm300d2.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace sm300d2 { + +class SM300D2Sensor : public PollingComponent, public uart::UARTDevice { + public: + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; } + void set_formaldehyde_sensor(sensor::Sensor *formaldehyde_sensor) { formaldehyde_sensor_ = formaldehyde_sensor; } + void set_tvoc_sensor(sensor::Sensor *tvoc_sensor) { tvoc_sensor_ = tvoc_sensor; } + void set_pm_2_5_sensor(sensor::Sensor *pm_2_5_sensor) { pm_2_5_sensor_ = pm_2_5_sensor; } + void set_pm_10_0_sensor(sensor::Sensor *pm_10_0_sensor) { pm_10_0_sensor_ = pm_10_0_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } + + void update() override; + void dump_config() override; + + protected: + uint16_t sm300d2_checksum_(uint8_t *ptr); + + sensor::Sensor *co2_sensor_{nullptr}; + sensor::Sensor *formaldehyde_sensor_{nullptr}; + sensor::Sensor *tvoc_sensor_{nullptr}; + sensor::Sensor *pm_2_5_sensor_{nullptr}; + sensor::Sensor *pm_10_0_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; +}; + +} // namespace sm300d2 +} // namespace esphome diff --git a/esphome/components/sn74hc595/__init__.py b/esphome/components/sn74hc595/__init__.py index 369fc41facf7..152ac02106af 100644 --- a/esphome/components/sn74hc595/__init__.py +++ b/esphome/components/sn74hc595/__init__.py @@ -1,28 +1,36 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.const import CONF_ID, CONF_NUMBER, CONF_INVERTED, CONF_DATA_PIN, CONF_CLOCK_PIN +from esphome.const import ( + CONF_ID, + CONF_NUMBER, + CONF_INVERTED, + CONF_DATA_PIN, + CONF_CLOCK_PIN, +) DEPENDENCIES = [] MULTI_CONF = True -sn74hc595_ns = cg.esphome_ns.namespace('sn74hc595') +sn74hc595_ns = cg.esphome_ns.namespace("sn74hc595") -SN74HC595Component = sn74hc595_ns.class_('SN74HC595Component', cg.Component) -SN74HC595GPIOPin = sn74hc595_ns.class_('SN74HC595GPIOPin', cg.GPIOPin) +SN74HC595Component = sn74hc595_ns.class_("SN74HC595Component", cg.Component) +SN74HC595GPIOPin = sn74hc595_ns.class_("SN74HC595GPIOPin", cg.GPIOPin) -CONF_SN74HC595 = 'sn74hc595' -CONF_LATCH_PIN = 'latch_pin' -CONF_OE_PIN = 'oe_pin' -CONF_SR_COUNT = 'sr_count' -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_ID): cv.declare_id(SN74HC595Component), - cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_LATCH_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_OE_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_SR_COUNT, default=1): cv.int_range(1, 4) -}).extend(cv.COMPONENT_SCHEMA) +CONF_SN74HC595 = "sn74hc595" +CONF_LATCH_PIN = "latch_pin" +CONF_OE_PIN = "oe_pin" +CONF_SR_COUNT = "sr_count" +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(SN74HC595Component), + cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_LATCH_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_OE_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_SR_COUNT, default=1): cv.int_range(1, 4), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -40,16 +48,19 @@ def to_code(config): cg.add(var.set_sr_count(config[CONF_SR_COUNT])) -SN74HC595_OUTPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_SN74HC595): cv.use_id(SN74HC595Component), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) +SN74HC595_OUTPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_SN74HC595): cv.use_id(SN74HC595Component), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) SN74HC595_INPUT_PIN_SCHEMA = cv.Schema({}) -@pins.PIN_SCHEMA_REGISTRY.register(CONF_SN74HC595, - (SN74HC595_OUTPUT_PIN_SCHEMA, SN74HC595_INPUT_PIN_SCHEMA)) +@pins.PIN_SCHEMA_REGISTRY.register( + CONF_SN74HC595, (SN74HC595_OUTPUT_PIN_SCHEMA, SN74HC595_INPUT_PIN_SCHEMA) +) def sn74hc595_pin_to_code(config): parent = yield cg.get_variable(config[CONF_SN74HC595]) yield SN74HC595GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_INVERTED]) diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index f6afcced0cd7..a142ad72d753 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -5,25 +5,28 @@ from esphome.const import CONF_ID, CONF_SERVERS -DEPENDENCIES = ['network'] -sntp_ns = cg.esphome_ns.namespace('sntp') -SNTPComponent = sntp_ns.class_('SNTPComponent', time_.RealTimeClock) +DEPENDENCIES = ["network"] +sntp_ns = cg.esphome_ns.namespace("sntp") +SNTPComponent = sntp_ns.class_("SNTPComponent", time_.RealTimeClock) DEFAULT_SERVERS = ["0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org"] -CONFIG_SCHEMA = time_.TIME_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SNTPComponent), - cv.Optional(CONF_SERVERS, default=DEFAULT_SERVERS): - cv.All(cv.ensure_list(cv.domain), cv.Length(min=1, max=3)), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = time_.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SNTPComponent), + cv.Optional(CONF_SERVERS, default=DEFAULT_SERVERS): cv.All( + cv.ensure_list(cv.domain), cv.Length(min=1, max=3) + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) servers = config[CONF_SERVERS] - servers += [''] * (3 - len(servers)) + servers += [""] * (3 - len(servers)) cg.add(var.set_servers(*servers)) yield cg.register_component(var, config) @@ -31,4 +34,4 @@ def to_code(config): if CORE.is_esp8266 and len(servers) > 1: # We need LwIP features enabled to get 3 SNTP servers (not just one) - cg.add_build_flag('-DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY') + cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY") diff --git a/esphome/components/speed/__init__.py b/esphome/components/speed/__init__.py index 7c7d64ed14c1..b75374863be6 100644 --- a/esphome/components/speed/__init__.py +++ b/esphome/components/speed/__init__.py @@ -1,3 +1,3 @@ import esphome.codegen as cg -speed_ns = cg.esphome_ns.namespace('speed') +speed_ns = cg.esphome_ns.namespace("speed") diff --git a/esphome/components/speed/fan/__init__.py b/esphome/components/speed/fan/__init__.py index 420c957d8792..fdb0a9af09fe 100644 --- a/esphome/components/speed/fan/__init__.py +++ b/esphome/components/speed/fan/__init__.py @@ -1,32 +1,39 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import fan, output -from esphome.const import CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_DIRECTION_OUTPUT, \ - CONF_OUTPUT_ID, CONF_SPEED, CONF_LOW, CONF_MEDIUM, CONF_HIGH +from esphome.const import ( + CONF_OSCILLATION_OUTPUT, + CONF_OUTPUT, + CONF_DIRECTION_OUTPUT, + CONF_OUTPUT_ID, + CONF_SPEED, + CONF_SPEED_COUNT, +) from .. import speed_ns -SpeedFan = speed_ns.class_('SpeedFan', cg.Component) +SpeedFan = speed_ns.class_("SpeedFan", cg.Component) -CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpeedFan), - cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput), - cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), - cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), - cv.Optional(CONF_SPEED, default={}): cv.Schema({ - cv.Optional(CONF_LOW, default=0.33): cv.percentage, - cv.Optional(CONF_MEDIUM, default=0.66): cv.percentage, - cv.Optional(CONF_HIGH, default=1.0): cv.percentage, - }), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpeedFan), + cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput), + cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Optional(CONF_SPEED): cv.invalid( + "Configuring individual speeds is deprecated." + ), + cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): output_ = yield cg.get_variable(config[CONF_OUTPUT]) state = yield fan.create_fan_state(config) - var = cg.new_Pvariable(config[CONF_OUTPUT_ID], state, output_) + var = cg.new_Pvariable( + config[CONF_OUTPUT_ID], state, output_, config[CONF_SPEED_COUNT] + ) yield cg.register_component(var, config) - speeds = config[CONF_SPEED] - cg.add(var.set_speeds(speeds[CONF_LOW], speeds[CONF_MEDIUM], speeds[CONF_HIGH])) if CONF_OSCILLATION_OUTPUT in config: oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index 45117d64c3d5..0455fa200d4d 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -1,4 +1,5 @@ #include "speed_fan.h" +#include "esphome/components/fan/fan_helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -16,7 +17,7 @@ void SpeedFan::dump_config() { } } void SpeedFan::setup() { - auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr); + auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); this->fan_->set_traits(traits); this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); } @@ -29,12 +30,7 @@ void SpeedFan::loop() { { float speed = 0.0f; if (this->fan_->state) { - if (this->fan_->speed == fan::FAN_SPEED_LOW) - speed = this->low_speed_; - else if (this->fan_->speed == fan::FAN_SPEED_MEDIUM) - speed = this->medium_speed_; - else if (this->fan_->speed == fan::FAN_SPEED_HIGH) - speed = this->high_speed_; + speed = static_cast(this->fan_->speed) / static_cast(this->speed_count_); } ESP_LOGD(TAG, "Setting speed: %.2f", speed); this->output_->set_level(speed); diff --git a/esphome/components/speed/fan/speed_fan.h b/esphome/components/speed/fan/speed_fan.h index cce9d07544f9..6b7fa0b0f2a6 100644 --- a/esphome/components/speed/fan/speed_fan.h +++ b/esphome/components/speed/fan/speed_fan.h @@ -10,28 +10,22 @@ namespace speed { class SpeedFan : public Component { public: - SpeedFan(fan::FanState *fan, output::FloatOutput *output) : fan_(fan), output_(output) {} + SpeedFan(fan::FanState *fan, output::FloatOutput *output, int speed_count) + : fan_(fan), output_(output), speed_count_(speed_count) {} void setup() override; void loop() override; void dump_config() override; float get_setup_priority() const override; void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } - void set_speeds(float low, float medium, float high) { - this->low_speed_ = low; - this->medium_speed_ = medium; - this->high_speed_ = high; - } protected: fan::FanState *fan_; output::FloatOutput *output_; output::BinaryOutput *oscillating_{nullptr}; output::BinaryOutput *direction_{nullptr}; - float low_speed_{}; - float medium_speed_{}; - float high_speed_{}; bool next_update_{true}; + int speed_count_{}; }; } // namespace speed diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index e7f8bc378d1f..5a25ac254be1 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -1,22 +1,33 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.const import CONF_CLK_PIN, CONF_ID, CONF_MISO_PIN, CONF_MOSI_PIN, CONF_SPI_ID, \ - CONF_CS_PIN +from esphome.const import ( + CONF_CLK_PIN, + CONF_ID, + CONF_MISO_PIN, + CONF_MOSI_PIN, + CONF_SPI_ID, + CONF_CS_PIN, +) from esphome.core import coroutine, coroutine_with_priority -CODEOWNERS = ['@esphome/core'] -spi_ns = cg.esphome_ns.namespace('spi') -SPIComponent = spi_ns.class_('SPIComponent', cg.Component) -SPIDevice = spi_ns.class_('SPIDevice') +CODEOWNERS = ["@esphome/core"] +spi_ns = cg.esphome_ns.namespace("spi") +SPIComponent = spi_ns.class_("SPIComponent", cg.Component) +SPIDevice = spi_ns.class_("SPIDevice") MULTI_CONF = True -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(SPIComponent), - cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema, - cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema, -}), cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN)) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SPIComponent), + cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema, + } + ), + cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN), +) @coroutine_with_priority(1.0) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index ea6f8d68f6a5..d2850b8e7d21 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -38,7 +38,7 @@ void SPIComponent::setup() { #ifdef ARDUINO_ARCH_ESP8266 if (clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) { // pass - } else if (clk_pin == 14 && miso_pin == 12 && mosi_pin == 13) { + } else if (clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13)) { // pass } else { use_hw_spi = false; diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 64364d70c966..a4a2e11def30 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -98,6 +98,30 @@ class SPIComponent : public Component { this->transfer_(data); } + template + void write_byte16(const uint16_t data) { + if (this->hw_spi_ != nullptr) { + this->hw_spi_->write16(data); + return; + } + + this->write_byte(data >> 8); + this->write_byte(data); + } + + template + void write_array16(const uint16_t *data, size_t length) { + if (this->hw_spi_ != nullptr) { + for (size_t i = 0; i < length; i++) { + this->hw_spi_->write16(data[i]); + } + return; + } + for (size_t i = 0; i < length; i++) { + this->write_byte16(data[i]); + } + } + template void write_array(const uint8_t *data, size_t length) { if (this->hw_spi_ != nullptr) { @@ -112,20 +136,34 @@ class SPIComponent : public Component { template uint8_t transfer_byte(uint8_t data) { - if (this->hw_spi_ != nullptr) { - return this->hw_spi_->transfer(data); + if (this->miso_ != nullptr) { + if (this->hw_spi_ != nullptr) { + return this->hw_spi_->transfer(data); + } else { + return this->transfer_(data); + } } - return this->transfer_(data); + this->write_byte(data); + return 0; } template void transfer_array(uint8_t *data, size_t length) { if (this->hw_spi_ != nullptr) { - this->hw_spi_->transfer(data, length); + if (this->miso_ != nullptr) { + this->hw_spi_->transfer(data, length); + } else { + this->hw_spi_->writeBytes(data, length); + } return; } - for (size_t i = 0; i < length; i++) { - data[i] = this->transfer_byte(data[i]); + + if (this->miso_ != nullptr) { + for (size_t i = 0; i < length; i++) { + data[i] = this->transfer_byte(data[i]); + } + } else { + this->write_array(data, length); } } @@ -208,6 +246,14 @@ class SPIDevice { return this->parent_->template write_byte(data); } + void write_byte16(uint8_t data) { + return this->parent_->template write_byte16(data); + } + + void write_array16(const uint16_t *data, size_t length) { + this->parent_->template write_array16(data, length); + } + void write_array(const uint8_t *data, size_t length) { this->parent_->template write_array(data, length); } diff --git a/esphome/components/sps30/sensor.py b/esphome/components/sps30/sensor.py index c45758be4e3d..5bc586ea8b33 100644 --- a/esphome/components/sps30/sensor.py +++ b/esphome/components/sps30/sensor.py @@ -1,39 +1,83 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, CONF_PM_1_0, CONF_PM_2_5, CONF_PM_4_0, CONF_PM_10_0, \ - CONF_PMC_0_5, CONF_PMC_1_0, CONF_PMC_2_5, CONF_PMC_4_0, CONF_PMC_10_0, CONF_PM_SIZE, \ - UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_COUNTS_PER_CUBIC_METER, UNIT_MICROMETER, \ - ICON_CHEMICAL_WEAPON, ICON_COUNTER, ICON_RULER +from esphome.const import ( + CONF_ID, + CONF_PM_1_0, + CONF_PM_2_5, + CONF_PM_4_0, + CONF_PM_10_0, + CONF_PMC_0_5, + CONF_PMC_1_0, + CONF_PMC_2_5, + CONF_PMC_4_0, + CONF_PMC_10_0, + CONF_PM_SIZE, + DEVICE_CLASS_EMPTY, + UNIT_MICROGRAMS_PER_CUBIC_METER, + UNIT_COUNTS_PER_CUBIC_METER, + UNIT_MICROMETER, + ICON_CHEMICAL_WEAPON, + ICON_COUNTER, + ICON_RULER, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -sps30_ns = cg.esphome_ns.namespace('sps30') -SPS30Component = sps30_ns.class_('SPS30Component', cg.PollingComponent, i2c.I2CDevice) +sps30_ns = cg.esphome_ns.namespace("sps30") +SPS30Component = sps30_ns.class_("SPS30Component", cg.PollingComponent, i2c.I2CDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(SPS30Component), - cv.Optional(CONF_PM_1_0): sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, 2), - cv.Optional(CONF_PM_2_5): sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, 2), - cv.Optional(CONF_PM_4_0): sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, 2), - cv.Optional(CONF_PM_10_0): sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, 2), - cv.Optional(CONF_PMC_0_5): sensor.sensor_schema(UNIT_COUNTS_PER_CUBIC_METER, - ICON_COUNTER, 2), - cv.Optional(CONF_PMC_1_0): sensor.sensor_schema(UNIT_COUNTS_PER_CUBIC_METER, - ICON_COUNTER, 2), - cv.Optional(CONF_PMC_2_5): sensor.sensor_schema(UNIT_COUNTS_PER_CUBIC_METER, - ICON_COUNTER, 2), - cv.Optional(CONF_PMC_4_0): sensor.sensor_schema(UNIT_COUNTS_PER_CUBIC_METER, - ICON_COUNTER, 2), - cv.Optional(CONF_PMC_10_0): sensor.sensor_schema(UNIT_COUNTS_PER_CUBIC_METER, - ICON_COUNTER, 2), - cv.Optional(CONF_PM_SIZE): sensor.sensor_schema(UNIT_MICROMETER, - ICON_RULER, 0), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x69)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SPS30Component), + cv.Optional(CONF_PM_1_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 2, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_2_5): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 2, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_4_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 2, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PM_10_0): sensor.sensor_schema( + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, + 2, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( + UNIT_COUNTS_PER_CUBIC_METER, ICON_COUNTER, 2, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( + UNIT_COUNTS_PER_CUBIC_METER, ICON_COUNTER, 2, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( + UNIT_COUNTS_PER_CUBIC_METER, ICON_COUNTER, 2, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PMC_4_0): sensor.sensor_schema( + UNIT_COUNTS_PER_CUBIC_METER, ICON_COUNTER, 2, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( + UNIT_COUNTS_PER_CUBIC_METER, ICON_COUNTER, 2, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_PM_SIZE): sensor.sensor_schema( + UNIT_MICROMETER, ICON_RULER, 0, DEVICE_CLASS_EMPTY + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x69)) +) def to_code(config): diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index a8b2a2a7bb2c..af7c1a019aef 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -2,33 +2,40 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display -from esphome.const import CONF_EXTERNAL_VCC, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN, \ - CONF_BRIGHTNESS +from esphome.const import ( + CONF_EXTERNAL_VCC, + CONF_LAMBDA, + CONF_MODEL, + CONF_RESET_PIN, + CONF_BRIGHTNESS, +) from esphome.core import coroutine -ssd1306_base_ns = cg.esphome_ns.namespace('ssd1306_base') -SSD1306 = ssd1306_base_ns.class_('SSD1306', cg.PollingComponent, display.DisplayBuffer) -SSD1306Model = ssd1306_base_ns.enum('SSD1306Model') +ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base") +SSD1306 = ssd1306_base_ns.class_("SSD1306", cg.PollingComponent, display.DisplayBuffer) +SSD1306Model = ssd1306_base_ns.enum("SSD1306Model") MODELS = { - 'SSD1306_128X32': SSD1306Model.SSD1306_MODEL_128_32, - 'SSD1306_128X64': SSD1306Model.SSD1306_MODEL_128_64, - 'SSD1306_96X16': SSD1306Model.SSD1306_MODEL_96_16, - 'SSD1306_64X48': SSD1306Model.SSD1306_MODEL_64_48, - 'SH1106_128X32': SSD1306Model.SH1106_MODEL_128_32, - 'SH1106_128X64': SSD1306Model.SH1106_MODEL_128_64, - 'SH1106_96X16': SSD1306Model.SH1106_MODEL_96_16, - 'SH1106_64X48': SSD1306Model.SH1106_MODEL_64_48, + "SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32, + "SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64, + "SSD1306_96X16": SSD1306Model.SSD1306_MODEL_96_16, + "SSD1306_64X48": SSD1306Model.SSD1306_MODEL_64_48, + "SH1106_128X32": SSD1306Model.SH1106_MODEL_128_32, + "SH1106_128X64": SSD1306Model.SH1106_MODEL_128_64, + "SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16, + "SH1106_64X48": SSD1306Model.SH1106_MODEL_64_48, } SSD1306_MODEL = cv.enum(MODELS, upper=True, space="_") -SSD1306_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({ - cv.Required(CONF_MODEL): SSD1306_MODEL, - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, - cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, -}).extend(cv.polling_component_schema('1s')) +SSD1306_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + { + cv.Required(CONF_MODEL): SSD1306_MODEL, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, + } +).extend(cv.polling_component_schema("1s")) @coroutine @@ -46,5 +53,6 @@ def setup_ssd1036(var, config): cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) if CONF_LAMBDA in config: lambda_ = yield cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], return_type=cg.void) + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1306_i2c/display.py b/esphome/components/ssd1306_i2c/display.py index eaa656f26b36..c51ef1cfba69 100644 --- a/esphome/components/ssd1306_i2c/display.py +++ b/esphome/components/ssd1306_i2c/display.py @@ -3,16 +3,22 @@ from esphome.components import ssd1306_base, i2c from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES -AUTO_LOAD = ['ssd1306_base'] -DEPENDENCIES = ['i2c'] +AUTO_LOAD = ["ssd1306_base"] +DEPENDENCIES = ["i2c"] -ssd1306_i2c = cg.esphome_ns.namespace('ssd1306_i2c') -I2CSSD1306 = ssd1306_i2c.class_('I2CSSD1306', ssd1306_base.SSD1306, i2c.I2CDevice) +ssd1306_i2c = cg.esphome_ns.namespace("ssd1306_i2c") +I2CSSD1306 = ssd1306_i2c.class_("I2CSSD1306", ssd1306_base.SSD1306, i2c.I2CDevice) -CONFIG_SCHEMA = cv.All(ssd1306_base.SSD1306_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(I2CSSD1306), -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x3C)), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + ssd1306_base.SSD1306_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(I2CSSD1306), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x3C)), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): diff --git a/esphome/components/ssd1306_spi/display.py b/esphome/components/ssd1306_spi/display.py index 19882af6c427..b1558ac41363 100644 --- a/esphome/components/ssd1306_spi/display.py +++ b/esphome/components/ssd1306_spi/display.py @@ -4,17 +4,23 @@ from esphome.components import spi, ssd1306_base from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES -AUTO_LOAD = ['ssd1306_base'] -DEPENDENCIES = ['spi'] +AUTO_LOAD = ["ssd1306_base"] +DEPENDENCIES = ["spi"] -ssd1306_spi = cg.esphome_ns.namespace('ssd1306_spi') -SPISSD1306 = ssd1306_spi.class_('SPISSD1306', ssd1306_base.SSD1306, spi.SPIDevice) +ssd1306_spi = cg.esphome_ns.namespace("ssd1306_spi") +SPISSD1306 = ssd1306_spi.class_("SPISSD1306", ssd1306_base.SSD1306, spi.SPIDevice) -CONFIG_SCHEMA = cv.All(ssd1306_base.SSD1306_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SPISSD1306), - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema()), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + ssd1306_base.SSD1306_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SPISSD1306), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema()), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): diff --git a/esphome/components/ssd1322_base/__init__.py b/esphome/components/ssd1322_base/__init__.py index addfec4153e7..0792fd0f895d 100644 --- a/esphome/components/ssd1322_base/__init__.py +++ b/esphome/components/ssd1322_base/__init__.py @@ -2,28 +2,35 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display -from esphome.const import CONF_BRIGHTNESS, CONF_EXTERNAL_VCC, CONF_LAMBDA, CONF_MODEL, \ - CONF_RESET_PIN +from esphome.const import ( + CONF_BRIGHTNESS, + CONF_EXTERNAL_VCC, + CONF_LAMBDA, + CONF_MODEL, + CONF_RESET_PIN, +) from esphome.core import coroutine -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -ssd1322_base_ns = cg.esphome_ns.namespace('ssd1322_base') -SSD1322 = ssd1322_base_ns.class_('SSD1322', cg.PollingComponent, display.DisplayBuffer) -SSD1322Model = ssd1322_base_ns.enum('SSD1322Model') +ssd1322_base_ns = cg.esphome_ns.namespace("ssd1322_base") +SSD1322 = ssd1322_base_ns.class_("SSD1322", cg.PollingComponent, display.DisplayBuffer) +SSD1322Model = ssd1322_base_ns.enum("SSD1322Model") MODELS = { - 'SSD1322_256X64': SSD1322Model.SSD1322_MODEL_256_64, + "SSD1322_256X64": SSD1322Model.SSD1322_MODEL_256_64, } SSD1322_MODEL = cv.enum(MODELS, upper=True, space="_") -SSD1322_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({ - cv.Required(CONF_MODEL): SSD1322_MODEL, - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, - cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, -}).extend(cv.polling_component_schema('1s')) +SSD1322_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + { + cv.Required(CONF_MODEL): SSD1322_MODEL, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, + } +).extend(cv.polling_component_schema("1s")) @coroutine @@ -41,5 +48,6 @@ def setup_ssd1322(var, config): cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) if CONF_LAMBDA in config: lambda_ = yield cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], return_type=cg.void) + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1322_base/ssd1322_base.cpp b/esphome/components/ssd1322_base/ssd1322_base.cpp index 9f382190a6d8..703f53fa9335 100644 --- a/esphome/components/ssd1322_base/ssd1322_base.cpp +++ b/esphome/components/ssd1322_base/ssd1322_base.cpp @@ -162,7 +162,7 @@ size_t SSD1322::get_buffer_length_() { void HOT SSD1322::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) return; - uint32_t color4 = color.to_grayscale4(); + uint32_t color4 = display::ColorUtil::color_to_grayscale4(color); // where should the bits go in the big buffer array? math... uint16_t pos = (x / SSD1322_PIXELSPERBYTE) + (y * this->get_width_internal() / SSD1322_PIXELSPERBYTE); uint8_t shift = (1u - (x % SSD1322_PIXELSPERBYTE)) * SSD1322_COLORSHIFT; @@ -174,7 +174,7 @@ void HOT SSD1322::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] |= color4; } void SSD1322::fill(Color color) { - const uint32_t color4 = color.to_grayscale4(); + const uint32_t color4 = display::ColorUtil::color_to_grayscale4(color); uint8_t fill = (color4 & SSD1322_COLORMASK) | ((color4 & SSD1322_COLORMASK) << SSD1322_COLORSHIFT); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) this->buffer_[i] = fill; diff --git a/esphome/components/ssd1322_spi/display.py b/esphome/components/ssd1322_spi/display.py index cf900e9e2d7b..fa094acc5ffe 100644 --- a/esphome/components/ssd1322_spi/display.py +++ b/esphome/components/ssd1322_spi/display.py @@ -4,19 +4,25 @@ from esphome.components import spi, ssd1322_base from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -AUTO_LOAD = ['ssd1322_base'] -DEPENDENCIES = ['spi'] +AUTO_LOAD = ["ssd1322_base"] +DEPENDENCIES = ["spi"] -ssd1322_spi = cg.esphome_ns.namespace('ssd1322_spi') -SPISSD1322 = ssd1322_spi.class_('SPISSD1322', ssd1322_base.SSD1322, spi.SPIDevice) +ssd1322_spi = cg.esphome_ns.namespace("ssd1322_spi") +SPISSD1322 = ssd1322_spi.class_("SPISSD1322", ssd1322_base.SSD1322, spi.SPIDevice) -CONFIG_SCHEMA = cv.All(ssd1322_base.SSD1322_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SPISSD1322), - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=False)), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + ssd1322_base.SSD1322_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SPISSD1322), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema(cs_pin_required=False)), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): diff --git a/esphome/components/ssd1325_base/__init__.py b/esphome/components/ssd1325_base/__init__.py index e51f67e8b40c..1e6535c7ecae 100644 --- a/esphome/components/ssd1325_base/__init__.py +++ b/esphome/components/ssd1325_base/__init__.py @@ -2,32 +2,39 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display -from esphome.const import CONF_BRIGHTNESS, CONF_EXTERNAL_VCC, CONF_LAMBDA, CONF_MODEL, \ - CONF_RESET_PIN +from esphome.const import ( + CONF_BRIGHTNESS, + CONF_EXTERNAL_VCC, + CONF_LAMBDA, + CONF_MODEL, + CONF_RESET_PIN, +) from esphome.core import coroutine -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -ssd1325_base_ns = cg.esphome_ns.namespace('ssd1325_base') -SSD1325 = ssd1325_base_ns.class_('SSD1325', cg.PollingComponent, display.DisplayBuffer) -SSD1325Model = ssd1325_base_ns.enum('SSD1325Model') +ssd1325_base_ns = cg.esphome_ns.namespace("ssd1325_base") +SSD1325 = ssd1325_base_ns.class_("SSD1325", cg.PollingComponent, display.DisplayBuffer) +SSD1325Model = ssd1325_base_ns.enum("SSD1325Model") MODELS = { - 'SSD1325_128X32': SSD1325Model.SSD1325_MODEL_128_32, - 'SSD1325_128X64': SSD1325Model.SSD1325_MODEL_128_64, - 'SSD1325_96X16': SSD1325Model.SSD1325_MODEL_96_16, - 'SSD1325_64X48': SSD1325Model.SSD1325_MODEL_64_48, - 'SSD1327_128X128': SSD1325Model.SSD1327_MODEL_128_128, + "SSD1325_128X32": SSD1325Model.SSD1325_MODEL_128_32, + "SSD1325_128X64": SSD1325Model.SSD1325_MODEL_128_64, + "SSD1325_96X16": SSD1325Model.SSD1325_MODEL_96_16, + "SSD1325_64X48": SSD1325Model.SSD1325_MODEL_64_48, + "SSD1327_128X128": SSD1325Model.SSD1327_MODEL_128_128, } SSD1325_MODEL = cv.enum(MODELS, upper=True, space="_") -SSD1325_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({ - cv.Required(CONF_MODEL): SSD1325_MODEL, - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, - cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, -}).extend(cv.polling_component_schema('1s')) +SSD1325_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + { + cv.Required(CONF_MODEL): SSD1325_MODEL, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_EXTERNAL_VCC): cv.boolean, + } +).extend(cv.polling_component_schema("1s")) @coroutine @@ -45,5 +52,6 @@ def setup_ssd1325(var, config): cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) if CONF_LAMBDA in config: lambda_ = yield cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], return_type=cg.void) + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1325_base/ssd1325_base.cpp b/esphome/components/ssd1325_base/ssd1325_base.cpp index dfb1ef00eea7..981888050565 100644 --- a/esphome/components/ssd1325_base/ssd1325_base.cpp +++ b/esphome/components/ssd1325_base/ssd1325_base.cpp @@ -192,7 +192,7 @@ size_t SSD1325::get_buffer_length_() { void HOT SSD1325::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) return; - uint32_t color4 = color.to_grayscale4(); + uint32_t color4 = display::ColorUtil::color_to_grayscale4(color); // where should the bits go in the big buffer array? math... uint16_t pos = (x / SSD1325_PIXELSPERBYTE) + (y * this->get_width_internal() / SSD1325_PIXELSPERBYTE); uint8_t shift = (x % SSD1325_PIXELSPERBYTE) * SSD1325_COLORSHIFT; @@ -204,7 +204,7 @@ void HOT SSD1325::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] |= color4; } void SSD1325::fill(Color color) { - const uint32_t color4 = color.to_grayscale4(); + const uint32_t color4 = display::ColorUtil::color_to_grayscale4(color); uint8_t fill = (color4 & SSD1325_COLORMASK) | ((color4 & SSD1325_COLORMASK) << SSD1325_COLORSHIFT); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) this->buffer_[i] = fill; diff --git a/esphome/components/ssd1325_spi/display.py b/esphome/components/ssd1325_spi/display.py index 9471cf9c7691..6ce133bec12d 100644 --- a/esphome/components/ssd1325_spi/display.py +++ b/esphome/components/ssd1325_spi/display.py @@ -4,19 +4,25 @@ from esphome.components import spi, ssd1325_base from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -AUTO_LOAD = ['ssd1325_base'] -DEPENDENCIES = ['spi'] +AUTO_LOAD = ["ssd1325_base"] +DEPENDENCIES = ["spi"] -ssd1325_spi = cg.esphome_ns.namespace('ssd1325_spi') -SPISSD1325 = ssd1325_spi.class_('SPISSD1325', ssd1325_base.SSD1325, spi.SPIDevice) +ssd1325_spi = cg.esphome_ns.namespace("ssd1325_spi") +SPISSD1325 = ssd1325_spi.class_("SPISSD1325", ssd1325_base.SSD1325, spi.SPIDevice) -CONFIG_SCHEMA = cv.All(ssd1325_base.SSD1325_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SPISSD1325), - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=False)), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + ssd1325_base.SSD1325_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SPISSD1325), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema(cs_pin_required=False)), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): diff --git a/esphome/components/ssd1327_base/__init__.py b/esphome/components/ssd1327_base/__init__.py index ee282f215e03..74446d6f0919 100644 --- a/esphome/components/ssd1327_base/__init__.py +++ b/esphome/components/ssd1327_base/__init__.py @@ -5,23 +5,25 @@ from esphome.const import CONF_BRIGHTNESS, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN from esphome.core import coroutine -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -ssd1327_base_ns = cg.esphome_ns.namespace('ssd1327_base') -SSD1327 = ssd1327_base_ns.class_('SSD1327', cg.PollingComponent, display.DisplayBuffer) -SSD1327Model = ssd1327_base_ns.enum('SSD1327Model') +ssd1327_base_ns = cg.esphome_ns.namespace("ssd1327_base") +SSD1327 = ssd1327_base_ns.class_("SSD1327", cg.PollingComponent, display.DisplayBuffer) +SSD1327Model = ssd1327_base_ns.enum("SSD1327Model") MODELS = { - 'SSD1327_128X128': SSD1327Model.SSD1327_MODEL_128_128, + "SSD1327_128X128": SSD1327Model.SSD1327_MODEL_128_128, } SSD1327_MODEL = cv.enum(MODELS, upper=True, space="_") -SSD1327_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({ - cv.Required(CONF_MODEL): SSD1327_MODEL, - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, -}).extend(cv.polling_component_schema('1s')) +SSD1327_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + { + cv.Required(CONF_MODEL): SSD1327_MODEL, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + } +).extend(cv.polling_component_schema("1s")) @coroutine @@ -37,5 +39,6 @@ def setup_ssd1327(var, config): cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: lambda_ = yield cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], return_type=cg.void) + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1327_base/ssd1327_base.cpp b/esphome/components/ssd1327_base/ssd1327_base.cpp index debe2455ffa3..398712c08394 100644 --- a/esphome/components/ssd1327_base/ssd1327_base.cpp +++ b/esphome/components/ssd1327_base/ssd1327_base.cpp @@ -136,7 +136,7 @@ size_t SSD1327::get_buffer_length_() { void HOT SSD1327::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) return; - uint32_t color4 = color.to_grayscale4(); + uint32_t color4 = display::ColorUtil::color_to_grayscale4(color); // where should the bits go in the big buffer array? math... uint16_t pos = (x / SSD1327_PIXELSPERBYTE) + (y * this->get_width_internal() / SSD1327_PIXELSPERBYTE); uint8_t shift = (x % SSD1327_PIXELSPERBYTE) * SSD1327_COLORSHIFT; @@ -148,7 +148,7 @@ void HOT SSD1327::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] |= color4; } void SSD1327::fill(Color color) { - const uint32_t color4 = color.to_grayscale4(); + const uint32_t color4 = display::ColorUtil::color_to_grayscale4(color); uint8_t fill = (color4 & SSD1327_COLORMASK) | ((color4 & SSD1327_COLORMASK) << SSD1327_COLORSHIFT); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) this->buffer_[i] = fill; diff --git a/esphome/components/ssd1327_i2c/display.py b/esphome/components/ssd1327_i2c/display.py index 9caa0ce031ff..f13ed003ad05 100644 --- a/esphome/components/ssd1327_i2c/display.py +++ b/esphome/components/ssd1327_i2c/display.py @@ -3,18 +3,24 @@ from esphome.components import ssd1327_base, i2c from esphome.const import CONF_ID, CONF_LAMBDA, CONF_PAGES -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -AUTO_LOAD = ['ssd1327_base'] -DEPENDENCIES = ['i2c'] +AUTO_LOAD = ["ssd1327_base"] +DEPENDENCIES = ["i2c"] -ssd1327_i2c = cg.esphome_ns.namespace('ssd1327_i2c') -I2CSSD1327 = ssd1327_i2c.class_('I2CSSD1327', ssd1327_base.SSD1327, i2c.I2CDevice) +ssd1327_i2c = cg.esphome_ns.namespace("ssd1327_i2c") +I2CSSD1327 = ssd1327_i2c.class_("I2CSSD1327", ssd1327_base.SSD1327, i2c.I2CDevice) -CONFIG_SCHEMA = cv.All(ssd1327_base.SSD1327_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(I2CSSD1327), -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x3D)), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + ssd1327_base.SSD1327_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(I2CSSD1327), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x3D)), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): diff --git a/esphome/components/ssd1327_spi/display.py b/esphome/components/ssd1327_spi/display.py index 5e3d21dae5be..fe119836a3d7 100644 --- a/esphome/components/ssd1327_spi/display.py +++ b/esphome/components/ssd1327_spi/display.py @@ -4,19 +4,25 @@ from esphome.components import spi, ssd1327_base from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -AUTO_LOAD = ['ssd1327_base'] -DEPENDENCIES = ['spi'] +AUTO_LOAD = ["ssd1327_base"] +DEPENDENCIES = ["spi"] -ssd1327_spi = cg.esphome_ns.namespace('ssd1327_spi') -SPISSD1327 = ssd1327_spi.class_('SPISSD1327', ssd1327_base.SSD1327, spi.SPIDevice) +ssd1327_spi = cg.esphome_ns.namespace("ssd1327_spi") +SPISSD1327 = ssd1327_spi.class_("SPISSD1327", ssd1327_base.SSD1327, spi.SPIDevice) -CONFIG_SCHEMA = cv.All(ssd1327_base.SSD1327_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SPISSD1327), - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=False)), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + ssd1327_base.SSD1327_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SPISSD1327), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema(cs_pin_required=False)), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): diff --git a/esphome/components/ssd1331_base/__init__.py b/esphome/components/ssd1331_base/__init__.py index f6423f4aafac..e151d3fe1784 100644 --- a/esphome/components/ssd1331_base/__init__.py +++ b/esphome/components/ssd1331_base/__init__.py @@ -5,15 +5,17 @@ from esphome.const import CONF_BRIGHTNESS, CONF_LAMBDA, CONF_RESET_PIN from esphome.core import coroutine -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -ssd1331_base_ns = cg.esphome_ns.namespace('ssd1331_base') -SSD1331 = ssd1331_base_ns.class_('SSD1331', cg.PollingComponent, display.DisplayBuffer) +ssd1331_base_ns = cg.esphome_ns.namespace("ssd1331_base") +SSD1331 = ssd1331_base_ns.class_("SSD1331", cg.PollingComponent, display.DisplayBuffer) -SSD1331_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({ - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, -}).extend(cv.polling_component_schema('1s')) +SSD1331_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + { + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + } +).extend(cv.polling_component_schema("1s")) @coroutine @@ -28,5 +30,6 @@ def setup_ssd1331(var, config): cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: lambda_ = yield cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], return_type=cg.void) + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1331_base/ssd1331_base.cpp b/esphome/components/ssd1331_base/ssd1331_base.cpp index 14051841771a..9db558104482 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.cpp +++ b/esphome/components/ssd1331_base/ssd1331_base.cpp @@ -123,14 +123,14 @@ size_t SSD1331::get_buffer_length_() { void HOT SSD1331::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) return; - const uint32_t color565 = color.to_rgb_565(); + const uint32_t color565 = display::ColorUtil::color_to_565(color); // where should the bits go in the big buffer array? math... uint16_t pos = (x + y * this->get_width_internal()) * SSD1331_BYTESPERPIXEL; this->buffer_[pos++] = (color565 >> 8) & 0xff; this->buffer_[pos] = color565 & 0xff; } void SSD1331::fill(Color color) { - const uint32_t color565 = color.to_rgb_565(); + const uint32_t color565 = display::ColorUtil::color_to_565(color); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) if (i & 1) { this->buffer_[i] = color565 & 0xff; diff --git a/esphome/components/ssd1331_spi/display.py b/esphome/components/ssd1331_spi/display.py index c10d34539e36..c4e9dad8bc03 100644 --- a/esphome/components/ssd1331_spi/display.py +++ b/esphome/components/ssd1331_spi/display.py @@ -4,19 +4,25 @@ from esphome.components import spi, ssd1331_base from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -AUTO_LOAD = ['ssd1331_base'] -DEPENDENCIES = ['spi'] +AUTO_LOAD = ["ssd1331_base"] +DEPENDENCIES = ["spi"] -ssd1331_spi = cg.esphome_ns.namespace('ssd1331_spi') -SPISSD1331 = ssd1331_spi.class_('SPISSD1331', ssd1331_base.SSD1331, spi.SPIDevice) +ssd1331_spi = cg.esphome_ns.namespace("ssd1331_spi") +SPISSD1331 = ssd1331_spi.class_("SPISSD1331", ssd1331_base.SSD1331, spi.SPIDevice) -CONFIG_SCHEMA = cv.All(ssd1331_base.SSD1331_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SPISSD1331), - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema()), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + ssd1331_base.SSD1331_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SPISSD1331), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema()), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): diff --git a/esphome/components/ssd1351_base/__init__.py b/esphome/components/ssd1351_base/__init__.py index 3bff245b82e3..5e2c14cb6fa7 100644 --- a/esphome/components/ssd1351_base/__init__.py +++ b/esphome/components/ssd1351_base/__init__.py @@ -5,24 +5,26 @@ from esphome.const import CONF_BRIGHTNESS, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN from esphome.core import coroutine -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -ssd1351_base_ns = cg.esphome_ns.namespace('ssd1351_base') -SSD1351 = ssd1351_base_ns.class_('SSD1351', cg.PollingComponent, display.DisplayBuffer) -SSD1351Model = ssd1351_base_ns.enum('SSD1351Model') +ssd1351_base_ns = cg.esphome_ns.namespace("ssd1351_base") +SSD1351 = ssd1351_base_ns.class_("SSD1351", cg.PollingComponent, display.DisplayBuffer) +SSD1351Model = ssd1351_base_ns.enum("SSD1351Model") MODELS = { - 'SSD1351_128X96': SSD1351Model.SSD1351_MODEL_128_96, - 'SSD1351_128X128': SSD1351Model.SSD1351_MODEL_128_128, + "SSD1351_128X96": SSD1351Model.SSD1351_MODEL_128_96, + "SSD1351_128X128": SSD1351Model.SSD1351_MODEL_128_128, } SSD1351_MODEL = cv.enum(MODELS, upper=True, space="_") -SSD1351_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({ - cv.Required(CONF_MODEL): SSD1351_MODEL, - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, -}).extend(cv.polling_component_schema('1s')) +SSD1351_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + { + cv.Required(CONF_MODEL): SSD1351_MODEL, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + } +).extend(cv.polling_component_schema("1s")) @coroutine @@ -38,5 +40,6 @@ def setup_ssd1351(var, config): cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: lambda_ = yield cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], return_type=cg.void) + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1351_base/ssd1351_base.cpp b/esphome/components/ssd1351_base/ssd1351_base.cpp index fded8e3482d6..42407a13de49 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.cpp +++ b/esphome/components/ssd1351_base/ssd1351_base.cpp @@ -151,14 +151,14 @@ size_t SSD1351::get_buffer_length_() { void HOT SSD1351::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) return; - const uint32_t color565 = color.to_rgb_565(); + const uint32_t color565 = display::ColorUtil::color_to_565(color); // where should the bits go in the big buffer array? math... uint16_t pos = (x + y * this->get_width_internal()) * SSD1351_BYTESPERPIXEL; this->buffer_[pos++] = (color565 >> 8) & 0xff; this->buffer_[pos] = color565 & 0xff; } void SSD1351::fill(Color color) { - const uint32_t color565 = color.to_rgb_565(); + const uint32_t color565 = display::ColorUtil::color_to_565(color); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) if (i & 1) { this->buffer_[i] = color565 & 0xff; diff --git a/esphome/components/ssd1351_spi/display.py b/esphome/components/ssd1351_spi/display.py index 30b4c7308566..66e4fad02c09 100644 --- a/esphome/components/ssd1351_spi/display.py +++ b/esphome/components/ssd1351_spi/display.py @@ -4,19 +4,25 @@ from esphome.components import spi, ssd1351_base from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_PAGES -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -AUTO_LOAD = ['ssd1351_base'] -DEPENDENCIES = ['spi'] +AUTO_LOAD = ["ssd1351_base"] +DEPENDENCIES = ["spi"] -ssd1351_spi = cg.esphome_ns.namespace('ssd1351_spi') -SPISSD1351 = ssd1351_spi.class_('SPISSD1351', ssd1351_base.SSD1351, spi.SPIDevice) +ssd1351_spi = cg.esphome_ns.namespace("ssd1351_spi") +SPISSD1351 = ssd1351_spi.class_("SPISSD1351", ssd1351_base.SSD1351, spi.SPIDevice) -CONFIG_SCHEMA = cv.All(ssd1351_base.SSD1351_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SPISSD1351), - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema()), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + ssd1351_base.SSD1351_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SPISSD1351), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema()), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): diff --git a/esphome/components/st7735/__init__.py b/esphome/components/st7735/__init__.py index 9f1d58b67163..ba854bb0ae0c 100644 --- a/esphome/components/st7735/__init__.py +++ b/esphome/components/st7735/__init__.py @@ -1,2 +1,3 @@ import esphome.codegen as cg -st7735_ns = cg.esphome_ns.namespace('st7735') + +st7735_ns = cg.esphome_ns.namespace("st7735") diff --git a/esphome/components/st7735/display.py b/esphome/components/st7735/display.py index 902f9c8bebc6..f1e7d37d5106 100644 --- a/esphome/components/st7735/display.py +++ b/esphome/components/st7735/display.py @@ -4,50 +4,67 @@ from esphome.components import spi from esphome.components import display from esphome.core import coroutine -from esphome.const import CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_RESET_PIN, CONF_PAGES +from esphome.const import ( + CONF_DC_PIN, + CONF_ID, + CONF_LAMBDA, + CONF_MODEL, + CONF_RESET_PIN, + CONF_PAGES, +) from . import st7735_ns -CODEOWNERS = ['@SenexCrenshaw'] +CODEOWNERS = ["@SenexCrenshaw"] -DEPENDENCIES = ['spi'] +DEPENDENCIES = ["spi"] -CONF_DEVICEWIDTH = 'devicewidth' -CONF_DEVICEHEIGHT = 'deviceheight' -CONF_ROWSTART = 'rowstart' -CONF_COLSTART = 'colstart' -CONF_EIGHTBITCOLOR = 'eightbitcolor' -CONF_USEBGR = 'usebgr' +CONF_DEVICE_WIDTH = "device_width" +CONF_DEVICE_HEIGHT = "device_height" +CONF_ROW_START = "row_start" +CONF_COL_START = "col_start" +CONF_EIGHT_BIT_COLOR = "eight_bit_color" +CONF_USE_BGR = "use_bgr" -SPIST7735 = st7735_ns.class_('ST7735', cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice) -ST7735Model = st7735_ns.enum('ST7735Model') +SPIST7735 = st7735_ns.class_( + "ST7735", cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice +) +ST7735Model = st7735_ns.enum("ST7735Model") MODELS = { - 'INITR_GREENTAB': ST7735Model.ST7735_INITR_GREENTAB, - 'INITR_REDTAB': ST7735Model.ST7735_INITR_REDTAB, - 'INITR_BLACKTAB': ST7735Model.ST7735_INITR_BLACKTAB, - 'INITR_MINI160X80': ST7735Model.ST7735_INITR_MINI_160X80, - 'INITR_18BLACKTAB': ST7735Model.ST7735_INITR_18BLACKTAB, - 'INITR_18REDTAB': ST7735Model.ST7735_INITR_18REDTAB + "INITR_GREENTAB": ST7735Model.ST7735_INITR_GREENTAB, + "INITR_REDTAB": ST7735Model.ST7735_INITR_REDTAB, + "INITR_BLACKTAB": ST7735Model.ST7735_INITR_BLACKTAB, + "INITR_MINI160X80": ST7735Model.ST7735_INITR_MINI_160X80, + "INITR_18BLACKTAB": ST7735Model.ST7735_INITR_18BLACKTAB, + "INITR_18REDTAB": ST7735Model.ST7735_INITR_18REDTAB, } ST7735_MODEL = cv.enum(MODELS, upper=True, space="_") -ST7735_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({ - cv.Required(CONF_MODEL): ST7735_MODEL, - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema -}).extend(cv.polling_component_schema('1s')) +ST7735_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( + { + cv.Required(CONF_MODEL): ST7735_MODEL, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + } +).extend(cv.polling_component_schema("1s")) -CONFIG_SCHEMA = cv.All(ST7735_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SPIST7735), - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_DEVICEWIDTH): cv.int_, - cv.Required(CONF_DEVICEHEIGHT): cv.int_, - cv.Required(CONF_COLSTART): cv.int_, - cv.Required(CONF_ROWSTART): cv.int_, - cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean, - cv.Optional(CONF_USEBGR, default=False): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema()), - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + ST7735_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SPIST7735), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DEVICE_WIDTH): cv.int_, + cv.Required(CONF_DEVICE_HEIGHT): cv.int_, + cv.Required(CONF_COL_START): cv.int_, + cv.Required(CONF_ROW_START): cv.int_, + cv.Optional(CONF_EIGHT_BIT_COLOR, default=False): cv.boolean, + cv.Optional(CONF_USE_BGR, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema()), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) @coroutine @@ -60,14 +77,22 @@ def setup_st7735(var, config): cg.add(var.set_reset_pin(reset)) if CONF_LAMBDA in config: lambda_ = yield cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], return_type=cg.void) + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) def to_code(config): - var = cg.new_Pvariable(config[CONF_ID], config[CONF_MODEL], config[CONF_DEVICEWIDTH], - config[CONF_DEVICEHEIGHT], config[CONF_COLSTART], config[CONF_ROWSTART], - config[CONF_EIGHTBITCOLOR], config[CONF_USEBGR]) + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_MODEL], + config[CONF_DEVICE_WIDTH], + config[CONF_DEVICE_HEIGHT], + config[CONF_COL_START], + config[CONF_ROW_START], + config[CONF_EIGHT_BIT_COLOR], + config[CONF_USE_BGR], + ) yield setup_st7735(var, config) yield spi.register_spi_device(var, config) diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 5cd79da2cba9..201098f3cf39 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -308,11 +308,11 @@ void HOT ST7735::draw_absolute_pixel_internal(int x, int y, Color color) { return; if (this->eightbitcolor_) { - const uint32_t color332 = color.to_332(); + const uint32_t color332 = display::ColorUtil::color_to_332(color); uint16_t pos = (x + y * this->get_width_internal()); this->buffer_[pos] = color332; } else { - const uint32_t color565 = color.to_565(); + const uint32_t color565 = display::ColorUtil::color_to_565(color); uint16_t pos = (x + y * this->get_width_internal()) * 2; this->buffer_[pos++] = (color565 >> 8) & 0xff; this->buffer_[pos] = color565 & 0xff; @@ -444,9 +444,11 @@ void HOT ST7735::write_display_data_() { if (this->eightbitcolor_) { for (int line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) { for (int index = 0; index < this->get_width_internal(); ++index) { - auto color = Color(this->buffer_[index + line], Color::ColorOrder::COLOR_ORDER_RGB, - Color::ColorBitness::COLOR_BITNESS_332, true) - .to_565(); + auto color332 = display::ColorUtil::to_color(this->buffer_[index + line], display::ColorOrder::COLOR_ORDER_RGB, + display::ColorBitness::COLOR_BITNESS_332, true); + + auto color = display::ColorUtil::color_to_565(color332); + this->write_byte((color >> 8) & 0xff); this->write_byte(color & 0xff); } diff --git a/esphome/components/st7789v/__init__.py b/esphome/components/st7789v/__init__.py index dc85fa6b7683..3e64d09c57f4 100644 --- a/esphome/components/st7789v/__init__.py +++ b/esphome/components/st7789v/__init__.py @@ -1,3 +1,3 @@ import esphome.codegen as cg -st7789v_ns = cg.esphome_ns.namespace('st7789v') +st7789v_ns = cg.esphome_ns.namespace("st7789v") diff --git a/esphome/components/st7789v/display.py b/esphome/components/st7789v/display.py index 5815ae599c7f..9b3d550374ce 100644 --- a/esphome/components/st7789v/display.py +++ b/esphome/components/st7789v/display.py @@ -2,26 +2,40 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display, spi -from esphome.const import CONF_BACKLIGHT_PIN, CONF_BRIGHTNESS, CONF_CS_PIN, CONF_DC_PIN, CONF_ID, \ - CONF_LAMBDA, CONF_RESET_PIN +from esphome.const import ( + CONF_BACKLIGHT_PIN, + CONF_BRIGHTNESS, + CONF_CS_PIN, + CONF_DC_PIN, + CONF_ID, + CONF_LAMBDA, + CONF_RESET_PIN, +) from . import st7789v_ns -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -DEPENDENCIES = ['spi'] +DEPENDENCIES = ["spi"] -ST7789V = st7789v_ns.class_('ST7789V', cg.PollingComponent, spi.SPIDevice, - display.DisplayBuffer) -ST7789VRef = ST7789V.operator('ref') +ST7789V = st7789v_ns.class_( + "ST7789V", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer +) +ST7789VRef = ST7789V.operator("ref") -CONFIG_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(ST7789V), - cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, -}).extend(cv.polling_component_schema('5s')).extend(spi.spi_device_schema()) +CONFIG_SCHEMA = ( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ST7789V), + cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + } + ) + .extend(cv.polling_component_schema("5s")) + .extend(spi.spi_device_schema()) +) def to_code(config): @@ -40,7 +54,8 @@ def to_code(config): if CONF_LAMBDA in config: lambda_ = yield cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], return_type=cg.void) + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) yield display.register_display(var, config) diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index 284f2342fc74..3a6374263acb 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -263,7 +263,7 @@ void HOT ST7789V::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) return; - auto color565 = color.to_rgb_565(); + auto color565 = display::ColorUtil::color_to_565(color); uint16_t pos = (x + y * this->get_width_internal()) * 2; this->buffer_[pos++] = (color565 >> 8) & 0xff; diff --git a/esphome/components/status/binary_sensor.py b/esphome/components/status/binary_sensor.py index 9963461cefaf..3a52b0b61745 100644 --- a/esphome/components/status/binary_sensor.py +++ b/esphome/components/status/binary_sensor.py @@ -3,15 +3,19 @@ from esphome.components import binary_sensor from esphome.const import CONF_ID, CONF_DEVICE_CLASS, DEVICE_CLASS_CONNECTIVITY -status_ns = cg.esphome_ns.namespace('status') -StatusBinarySensor = status_ns.class_('StatusBinarySensor', binary_sensor.BinarySensor, - cg.Component) +status_ns = cg.esphome_ns.namespace("status") +StatusBinarySensor = status_ns.class_( + "StatusBinarySensor", binary_sensor.BinarySensor, cg.Component +) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(StatusBinarySensor), - - cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_CONNECTIVITY): binary_sensor.device_class, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(StatusBinarySensor), + cv.Optional( + CONF_DEVICE_CLASS, default=DEVICE_CLASS_CONNECTIVITY + ): binary_sensor.device_class, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/status_led/__init__.py b/esphome/components/status_led/__init__.py index 26105b5e922e..76fbc01f39b4 100644 --- a/esphome/components/status_led/__init__.py +++ b/esphome/components/status_led/__init__.py @@ -4,13 +4,15 @@ from esphome.const import CONF_ID, CONF_PIN from esphome.core import coroutine_with_priority -status_led_ns = cg.esphome_ns.namespace('status_led') -StatusLED = status_led_ns.class_('StatusLED', cg.Component) +status_led_ns = cg.esphome_ns.namespace("status_led") +StatusLED = status_led_ns.class_("StatusLED", cg.Component) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(StatusLED), - cv.Required(CONF_PIN): pins.gpio_output_pin_schema, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(StatusLED), + cv.Required(CONF_PIN): pins.gpio_output_pin_schema, + } +).extend(cv.COMPONENT_SCHEMA) @coroutine_with_priority(80.0) @@ -20,4 +22,4 @@ def to_code(config): var = cg.Pvariable(config[CONF_ID], rhs) yield cg.register_component(var, config) cg.add(var.pre_setup()) - cg.add_define('USE_STATUS_LED') + cg.add_define("USE_STATUS_LED") diff --git a/esphome/components/stepper/__init__.py b/esphome/components/stepper/__init__.py index c61aaa7fc90f..53ddc82e0976 100644 --- a/esphome/components/stepper/__init__.py +++ b/esphome/components/stepper/__init__.py @@ -1,28 +1,35 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ACCELERATION, CONF_DECELERATION, CONF_ID, CONF_MAX_SPEED, \ - CONF_POSITION, CONF_TARGET, CONF_SPEED +from esphome.const import ( + CONF_ACCELERATION, + CONF_DECELERATION, + CONF_ID, + CONF_MAX_SPEED, + CONF_POSITION, + CONF_TARGET, + CONF_SPEED, +) from esphome.core import CORE, coroutine, coroutine_with_priority IS_PLATFORM_COMPONENT = True # pylint: disable=invalid-name -stepper_ns = cg.esphome_ns.namespace('stepper') -Stepper = stepper_ns.class_('Stepper') +stepper_ns = cg.esphome_ns.namespace("stepper") +Stepper = stepper_ns.class_("Stepper") -SetTargetAction = stepper_ns.class_('SetTargetAction', automation.Action) -ReportPositionAction = stepper_ns.class_('ReportPositionAction', automation.Action) -SetSpeedAction = stepper_ns.class_('SetSpeedAction', automation.Action) +SetTargetAction = stepper_ns.class_("SetTargetAction", automation.Action) +ReportPositionAction = stepper_ns.class_("ReportPositionAction", automation.Action) +SetSpeedAction = stepper_ns.class_("SetSpeedAction", automation.Action) 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)'): + 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)] + value = value[: -len(suffix)] - if value == 'inf': + if value == "inf": return 1e6 try: @@ -39,11 +46,11 @@ def validate_acceleration(value): def validate_speed(value): value = cv.string(value) - for suffix in ('steps/s', 'steps/s'): + for suffix in ("steps/s", "steps/s"): if value.endswith(suffix): - value = value[:-len(suffix)] + value = value[: -len(suffix)] - if value == 'inf': + if value == "inf": return 1e6 try: @@ -58,11 +65,13 @@ def validate_speed(value): return value -STEPPER_SCHEMA = cv.Schema({ - cv.Required(CONF_MAX_SPEED): validate_speed, - cv.Optional(CONF_ACCELERATION, default='inf'): validate_acceleration, - cv.Optional(CONF_DECELERATION, default='inf'): validate_acceleration, -}) +STEPPER_SCHEMA = cv.Schema( + { + cv.Required(CONF_MAX_SPEED): validate_speed, + cv.Optional(CONF_ACCELERATION, default="inf"): validate_acceleration, + cv.Optional(CONF_DECELERATION, default="inf"): validate_acceleration, + } +) @coroutine @@ -82,10 +91,16 @@ def register_stepper(var, config): yield setup_stepper_core_(var, config) -@automation.register_action('stepper.set_target', SetTargetAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(Stepper), - cv.Required(CONF_TARGET): cv.templatable(cv.int_), -})) +@automation.register_action( + "stepper.set_target", + SetTargetAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Stepper), + cv.Required(CONF_TARGET): cv.templatable(cv.int_), + } + ), +) def stepper_set_target_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -94,10 +109,16 @@ def stepper_set_target_to_code(config, action_id, template_arg, args): yield var -@automation.register_action('stepper.report_position', ReportPositionAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(Stepper), - cv.Required(CONF_POSITION): cv.templatable(cv.int_), -})) +@automation.register_action( + "stepper.report_position", + ReportPositionAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Stepper), + cv.Required(CONF_POSITION): cv.templatable(cv.int_), + } + ), +) def stepper_report_position_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -106,10 +127,16 @@ def stepper_report_position_to_code(config, action_id, template_arg, args): yield var -@automation.register_action('stepper.set_speed', SetSpeedAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(Stepper), - cv.Required(CONF_SPEED): cv.templatable(validate_speed), -})) +@automation.register_action( + "stepper.set_speed", + SetSpeedAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Stepper), + cv.Required(CONF_SPEED): cv.templatable(validate_speed), + } + ), +) def stepper_set_speed_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/sts3x/sensor.py b/esphome/components/sts3x/sensor.py index f48deeeae57d..5e44c3ca3c98 100644 --- a/esphome/components/sts3x/sensor.py +++ b/esphome/components/sts3x/sensor.py @@ -1,18 +1,26 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, ICON_THERMOMETER, UNIT_CELSIUS +from esphome.const import CONF_ID, DEVICE_CLASS_TEMPERATURE, ICON_EMPTY, UNIT_CELSIUS -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -sts3x_ns = cg.esphome_ns.namespace('sts3x') +sts3x_ns = cg.esphome_ns.namespace("sts3x") -STS3XComponent = sts3x_ns.class_('STS3XComponent', sensor.Sensor, - cg.PollingComponent, i2c.I2CDevice) +STS3XComponent = sts3x_ns.class_( + "STS3XComponent", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.GenerateID(): cv.declare_id(STS3XComponent), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x4A)) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE) + .extend( + { + cv.GenerateID(): cv.declare_id(STS3XComponent), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x4A)) +) def to_code(config): diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index 9c5d444b2c8c..61ad4a698a61 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -4,19 +4,21 @@ import esphome.config_validation as cv from esphome import core from esphome.const import CONF_SUBSTITUTIONS +from esphome.yaml_util import ESPHomeDataBase, make_data_base -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] _LOGGER = logging.getLogger(__name__) -VALID_SUBSTITUTIONS_CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \ - '0123456789_' +VALID_SUBSTITUTIONS_CHARACTERS = ( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" +) def validate_substitution_key(value): value = cv.string(value) if not value: raise cv.Invalid("Substitution key must not be empty") - if value[0] == '$': + if value[0] == "$": value = value[1:] if value[0].isdigit(): raise cv.Invalid("First character in substitutions cannot be a digit.") @@ -24,24 +26,29 @@ def validate_substitution_key(value): if char not in VALID_SUBSTITUTIONS_CHARACTERS: raise cv.Invalid( "Substitution must only consist of upper/lowercase characters, the underscore " - "and numbers. The character '{}' cannot be used".format(char)) + "and numbers. The character '{}' cannot be used".format(char) + ) return value -CONFIG_SCHEMA = cv.Schema({ - validate_substitution_key: cv.string_strict, -}) +CONFIG_SCHEMA = cv.Schema( + { + validate_substitution_key: cv.string_strict, + } +) def to_code(config): pass -VARIABLE_PROG = re.compile('\\$([{0}]+|\\{{[{0}]*\\}})'.format(VALID_SUBSTITUTIONS_CHARACTERS)) +VARIABLE_PROG = re.compile( + "\\$([{0}]+|\\{{[{0}]*\\}})".format(VALID_SUBSTITUTIONS_CHARACTERS) +) def _expand_substitutions(substitutions, value, path): - if '$' not in value: + if "$" not in value: return value orig_value = value @@ -55,11 +62,16 @@ def _expand_substitutions(substitutions, value, path): i, j = m.span(0) name = m.group(1) - if name.startswith('{') and name.endswith('}'): + if name.startswith("{") and name.endswith("}"): name = name[1:-1] if name not in substitutions: - _LOGGER.warning("Found '%s' (see %s) which looks like a substitution, but '%s' was " - "not declared", orig_value, '->'.join(str(x) for x in path), name) + _LOGGER.warning( + "Found '%s' (see %s) which looks like a substitution, but '%s' was " + "not declared", + orig_value, + "->".join(str(x) for x in path), + name, + ) i = j continue @@ -68,6 +80,14 @@ def _expand_substitutions(substitutions, value, path): value = value[:i] + sub i = len(value) value += tail + + # orig_value can also already be a lambda with esp_range info, and only + # a plain string is sent in orig_value + if isinstance(orig_value, ESPHomeDataBase): + # even though string can get larger or smaller, the range should point + # to original document marks + return make_data_base(value, orig_value) + return value @@ -110,10 +130,12 @@ def do_substitution_pass(config, command_line_substitutions): substitutions = command_line_substitutions elif command_line_substitutions: substitutions = {**substitutions, **command_line_substitutions} - with cv.prepend_path('substitutions'): + with cv.prepend_path("substitutions"): if not isinstance(substitutions, dict): - raise cv.Invalid("Substitutions must be a key to value mapping, got {}" - "".format(type(substitutions))) + raise cv.Invalid( + "Substitutions must be a key to value mapping, got {}" + "".format(type(substitutions)) + ) replace_keys = [] for key, value in substitutions.items(): @@ -127,4 +149,6 @@ def do_substitution_pass(config, command_line_substitutions): del substitutions[old] config[CONF_SUBSTITUTIONS] = substitutions + # Move substitutions to the first place to replace substitutions in them correctly + config.move_to_end(CONF_SUBSTITUTIONS, False) _substitute_item(substitutions, config, []) diff --git a/esphome/components/sun/__init__.py b/esphome/components/sun/__init__.py index a92442ea564b..5241f1bb55ab 100644 --- a/esphome/components/sun/__init__.py +++ b/esphome/components/sun/__init__.py @@ -4,58 +4,67 @@ from esphome.components import time from esphome.const import CONF_TIME_ID, CONF_ID, CONF_TRIGGER_ID -CODEOWNERS = ['@OttoWinter'] -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' +CODEOWNERS = ["@OttoWinter"] +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" # Default sun elevation is a bit below horizon because sunset # means time when the entire sun disk is below the horizon DEFAULT_ELEVATION = -0.883 ELEVATION_MAP = { - 'sunrise': 0.0, - 'sunset': 0.0, - 'civil': -6.0, - 'nautical': -12.0, - 'astronomical': -18.0, + "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='_')(value)] + value = ELEVATION_MAP[ + cv.one_of(*ELEVATION_MAP, lower=True, space="_")(value) + ] 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=DEFAULT_ELEVATION): elevation, - }), - cv.Optional(CONF_ON_SUNSET): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SunTrigger), - cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): elevation, - }), -}) +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=DEFAULT_ELEVATION): elevation, + } + ), + cv.Optional(CONF_ON_SUNSET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SunTrigger), + cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): elevation, + } + ), + } +) def to_code(config): @@ -82,10 +91,18 @@ def to_code(config): 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=DEFAULT_ELEVATION): cv.templatable(elevation), -})) +@automation.register_condition( + "sun.is_above_horizon", + SunCondition, + cv.Schema( + { + cv.GenerateID(): cv.use_id(Sun), + cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): cv.templatable( + elevation + ), + } + ), +) def sun_above_horizon_to_code(config, condition_id, template_arg, args): var = cg.new_Pvariable(condition_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) @@ -95,10 +112,18 @@ def sun_above_horizon_to_code(config, condition_id, template_arg, args): yield var -@automation.register_condition('sun.is_below_horizon', SunCondition, cv.Schema({ - cv.GenerateID(): cv.use_id(Sun), - cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): cv.templatable(elevation), -})) +@automation.register_condition( + "sun.is_below_horizon", + SunCondition, + cv.Schema( + { + cv.GenerateID(): cv.use_id(Sun), + cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): cv.templatable( + elevation + ), + } + ), +) def sun_below_horizon_to_code(config, condition_id, template_arg, args): var = cg.new_Pvariable(condition_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/sun/sensor/__init__.py b/esphome/components/sun/sensor/__init__.py index 5ca315888dbf..02e1ef28ebd7 100644 --- a/esphome/components/sun/sensor/__init__.py +++ b/esphome/components/sun/sensor/__init__.py @@ -1,23 +1,35 @@ 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 esphome.const import ( + DEVICE_CLASS_EMPTY, + UNIT_DEGREES, + ICON_WEATHER_SUNSET, + CONF_ID, + CONF_TYPE, +) from .. import sun_ns, CONF_SUN_ID, Sun -DEPENDENCIES = ['sun'] +DEPENDENCIES = ["sun"] -SunSensor = sun_ns.class_('SunSensor', sensor.Sensor, cg.PollingComponent) -SensorType = sun_ns.enum('SensorType') +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, + "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')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_DEGREES, ICON_WEATHER_SUNSET, 1, DEVICE_CLASS_EMPTY) + .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): diff --git a/esphome/components/sun/text_sensor/__init__.py b/esphome/components/sun/text_sensor/__init__.py index 984250f9f71b..b527da699b10 100644 --- a/esphome/components/sun/text_sensor/__init__.py +++ b/esphome/components/sun/text_sensor/__init__.py @@ -1,16 +1,24 @@ 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 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, DEFAULT_ELEVATION -DEPENDENCIES = ['sun'] +DEPENDENCIES = ["sun"] -SunTextSensor = sun_ns.class_('SunTextSensor', text_sensor.TextSensor, cg.PollingComponent) +SunTextSensor = sun_ns.class_( + "SunTextSensor", text_sensor.TextSensor, cg.PollingComponent +) SUN_TYPES = { - 'sunset': False, - 'sunrise': True, + "sunset": False, + "sunrise": True, } @@ -18,19 +26,24 @@ 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, + "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=DEFAULT_ELEVATION): elevation, - cv.Optional(CONF_FORMAT, default='%X'): cv.string_strict, -}).extend(cv.polling_component_schema('60s')), validate_optional_icon) +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=DEFAULT_ELEVATION): elevation, + cv.Optional(CONF_FORMAT, default="%X"): cv.string_strict, + } + ).extend(cv.polling_component_schema("60s")), + validate_optional_icon, +) def to_code(config): diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 7378b2a1401a..cc47b059cbe5 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -3,40 +3,58 @@ from esphome import automation from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt -from esphome.const import CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_INVERTED, CONF_ON_TURN_OFF, \ - CONF_ON_TURN_ON, CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME +from esphome.const import ( + CONF_ICON, + CONF_ID, + CONF_INTERNAL, + CONF_INVERTED, + CONF_ON_TURN_OFF, + CONF_ON_TURN_ON, + CONF_TRIGGER_ID, + CONF_MQTT_ID, + CONF_NAME, +) from esphome.core import CORE, coroutine, coroutine_with_priority -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True -switch_ns = cg.esphome_ns.namespace('switch_') -Switch = switch_ns.class_('Switch', cg.Nameable) -SwitchPtr = Switch.operator('ptr') +switch_ns = cg.esphome_ns.namespace("switch_") +Switch = switch_ns.class_("Switch", cg.Nameable) +SwitchPtr = Switch.operator("ptr") -ToggleAction = switch_ns.class_('ToggleAction', automation.Action) -TurnOffAction = switch_ns.class_('TurnOffAction', automation.Action) -TurnOnAction = switch_ns.class_('TurnOnAction', automation.Action) -SwitchPublishAction = switch_ns.class_('SwitchPublishAction', automation.Action) +ToggleAction = switch_ns.class_("ToggleAction", automation.Action) +TurnOffAction = switch_ns.class_("TurnOffAction", automation.Action) +TurnOnAction = switch_ns.class_("TurnOnAction", automation.Action) +SwitchPublishAction = switch_ns.class_("SwitchPublishAction", automation.Action) -SwitchCondition = switch_ns.class_('SwitchCondition', Condition) -SwitchTurnOnTrigger = switch_ns.class_('SwitchTurnOnTrigger', automation.Trigger.template()) -SwitchTurnOffTrigger = switch_ns.class_('SwitchTurnOffTrigger', automation.Trigger.template()) +SwitchCondition = switch_ns.class_("SwitchCondition", Condition) +SwitchTurnOnTrigger = switch_ns.class_( + "SwitchTurnOnTrigger", automation.Trigger.template() +) +SwitchTurnOffTrigger = switch_ns.class_( + "SwitchTurnOffTrigger", automation.Trigger.template() +) icon = cv.icon -SWITCH_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ - cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTSwitchComponent), - - cv.Optional(CONF_ICON): icon, - cv.Optional(CONF_INVERTED): cv.boolean, - cv.Optional(CONF_ON_TURN_ON): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOnTrigger), - }), - cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger), - }), -}) +SWITCH_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), + cv.Optional(CONF_ICON): icon, + cv.Optional(CONF_INVERTED): cv.boolean, + cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOnTrigger), + } + ), + cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger), + } + ), + } +) @coroutine @@ -68,26 +86,28 @@ def register_switch(var, config): yield setup_switch_core_(var, config) -SWITCH_ACTION_SCHEMA = maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(Switch), -}) +SWITCH_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Switch), + } +) -@automation.register_action('switch.toggle', ToggleAction, SWITCH_ACTION_SCHEMA) -@automation.register_action('switch.turn_off', TurnOffAction, SWITCH_ACTION_SCHEMA) -@automation.register_action('switch.turn_on', TurnOnAction, SWITCH_ACTION_SCHEMA) +@automation.register_action("switch.toggle", ToggleAction, SWITCH_ACTION_SCHEMA) +@automation.register_action("switch.turn_off", TurnOffAction, SWITCH_ACTION_SCHEMA) +@automation.register_action("switch.turn_on", TurnOnAction, SWITCH_ACTION_SCHEMA) def switch_toggle_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(action_id, template_arg, paren) -@automation.register_condition('switch.is_on', SwitchCondition, SWITCH_ACTION_SCHEMA) +@automation.register_condition("switch.is_on", SwitchCondition, SWITCH_ACTION_SCHEMA) def switch_is_on_to_code(config, condition_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(condition_id, template_arg, paren, True) -@automation.register_condition('switch.is_off', SwitchCondition, SWITCH_ACTION_SCHEMA) +@automation.register_condition("switch.is_off", SwitchCondition, SWITCH_ACTION_SCHEMA) def switch_is_off_to_code(config, condition_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(condition_id, template_arg, paren, False) @@ -96,4 +116,4 @@ def switch_is_off_to_code(config, condition_id, template_arg, args): @coroutine_with_priority(100.0) def to_code(config): cg.add_global(switch_ns.using) - cg.add_define('USE_SWITCH') + cg.add_define("USE_SWITCH") diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index 11fcfe3955f1..06ee364e1b4e 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -4,39 +4,47 @@ from esphome.components import i2c from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED -CONF_KEYPAD = 'keypad' -CONF_KEY_ROWS = 'key_rows' -CONF_KEY_COLUMNS = 'key_columns' -CONF_SLEEP_TIME = 'sleep_time' -CONF_SCAN_TIME = 'scan_time' -CONF_DEBOUNCE_TIME = 'debounce_time' +CONF_KEYPAD = "keypad" +CONF_KEY_ROWS = "key_rows" +CONF_KEY_COLUMNS = "key_columns" +CONF_SLEEP_TIME = "sleep_time" +CONF_SCAN_TIME = "scan_time" +CONF_DEBOUNCE_TIME = "debounce_time" -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] MULTI_CONF = True -sx1509_ns = cg.esphome_ns.namespace('sx1509') -SX1509GPIOMode = sx1509_ns.enum('SX1509GPIOMode') +sx1509_ns = cg.esphome_ns.namespace("sx1509") +SX1509GPIOMode = sx1509_ns.enum("SX1509GPIOMode") SX1509_GPIO_MODES = { - 'INPUT': SX1509GPIOMode.SX1509_INPUT, - 'INPUT_PULLUP': SX1509GPIOMode.SX1509_INPUT_PULLUP, - 'OUTPUT': SX1509GPIOMode.SX1509_OUTPUT + "INPUT": SX1509GPIOMode.SX1509_INPUT, + "INPUT_PULLUP": SX1509GPIOMode.SX1509_INPUT_PULLUP, + "OUTPUT": SX1509GPIOMode.SX1509_OUTPUT, } -SX1509Component = sx1509_ns.class_('SX1509Component', cg.Component, i2c.I2CDevice) -SX1509GPIOPin = sx1509_ns.class_('SX1509GPIOPin', cg.GPIOPin) +SX1509Component = sx1509_ns.class_("SX1509Component", cg.Component, i2c.I2CDevice) +SX1509GPIOPin = sx1509_ns.class_("SX1509GPIOPin", cg.GPIOPin) -KEYPAD_SCHEMA = cv.Schema({ - cv.Required(CONF_KEY_ROWS): cv.int_range(min=1, max=8), - cv.Required(CONF_KEY_COLUMNS): cv.int_range(min=1, max=8), - cv.Optional(CONF_SLEEP_TIME): cv.int_range(min=128, max=8192), - cv.Optional(CONF_SCAN_TIME): cv.int_range(min=1, max=128), - cv.Optional(CONF_DEBOUNCE_TIME): cv.int_range(min=1, max=64), -}) +KEYPAD_SCHEMA = cv.Schema( + { + cv.Required(CONF_KEY_ROWS): cv.int_range(min=1, max=8), + cv.Required(CONF_KEY_COLUMNS): cv.int_range(min=1, max=8), + cv.Optional(CONF_SLEEP_TIME): cv.int_range(min=128, max=8192), + cv.Optional(CONF_SCAN_TIME): cv.int_range(min=1, max=128), + cv.Optional(CONF_DEBOUNCE_TIME): cv.int_range(min=1, max=64), + } +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(SX1509Component), - cv.Optional(CONF_KEYPAD): cv.Schema(KEYPAD_SCHEMA), -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x3E)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SX1509Component), + cv.Optional(CONF_KEYPAD): cv.Schema(KEYPAD_SCHEMA), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x3E)) +) def to_code(config): @@ -46,32 +54,44 @@ def to_code(config): if CONF_KEYPAD in config: keypad = config[CONF_KEYPAD] cg.add(var.set_rows_cols(keypad[CONF_KEY_ROWS], keypad[CONF_KEY_COLUMNS])) - if CONF_SLEEP_TIME in keypad and CONF_SCAN_TIME in keypad and CONF_DEBOUNCE_TIME in keypad: + if ( + CONF_SLEEP_TIME in keypad + and CONF_SCAN_TIME in keypad + and CONF_DEBOUNCE_TIME in keypad + ): cg.add(var.set_sleep_time(keypad[CONF_SLEEP_TIME])) cg.add(var.set_scan_time(keypad[CONF_SCAN_TIME])) cg.add(var.set_debounce_time(keypad[CONF_DEBOUNCE_TIME])) -CONF_SX1509 = 'sx1509' -CONF_SX1509_ID = 'sx1509_id' +CONF_SX1509 = "sx1509" +CONF_SX1509_ID = "sx1509_id" -SX1509_OUTPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_SX1509): cv.use_id(SX1509Component), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(SX1509_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) -SX1509_INPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_SX1509): cv.use_id(SX1509Component), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum(SX1509_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) +SX1509_OUTPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_SX1509): cv.use_id(SX1509Component), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum( + SX1509_GPIO_MODES, upper=True + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) +SX1509_INPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_SX1509): cv.use_id(SX1509Component), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="INPUT"): cv.enum(SX1509_GPIO_MODES, upper=True), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) -@pins.PIN_SCHEMA_REGISTRY.register(CONF_SX1509, - (SX1509_OUTPUT_PIN_SCHEMA, SX1509_INPUT_PIN_SCHEMA)) +@pins.PIN_SCHEMA_REGISTRY.register( + CONF_SX1509, (SX1509_OUTPUT_PIN_SCHEMA, SX1509_INPUT_PIN_SCHEMA) +) def sx1509_pin_to_code(config): parent = yield cg.get_variable(config[CONF_SX1509]) - yield SX1509GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], - config[CONF_INVERTED]) + yield SX1509GPIOPin.new( + parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED] + ) diff --git a/esphome/components/sx1509/binary_sensor/__init__.py b/esphome/components/sx1509/binary_sensor/__init__.py index 9a65524383ee..e2344cd4af3f 100644 --- a/esphome/components/sx1509/binary_sensor/__init__.py +++ b/esphome/components/sx1509/binary_sensor/__init__.py @@ -4,19 +4,21 @@ from esphome.const import CONF_ID from .. import SX1509Component, sx1509_ns, CONF_SX1509_ID -CONF_ROW = 'row' -CONF_COL = 'col' +CONF_ROW = "row" +CONF_COL = "col" -DEPENDENCIES = ['sx1509'] +DEPENDENCIES = ["sx1509"] -SX1509BinarySensor = sx1509_ns.class_('SX1509BinarySensor', binary_sensor.BinarySensor) +SX1509BinarySensor = sx1509_ns.class_("SX1509BinarySensor", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SX1509BinarySensor), - cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component), - cv.Required(CONF_ROW): cv.int_range(min=0, max=4), - cv.Required(CONF_COL): cv.int_range(min=0, max=4), -}) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SX1509BinarySensor), + cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component), + cv.Required(CONF_ROW): cv.int_range(min=0, max=4), + cv.Required(CONF_COL): cv.int_range(min=0, max=4), + } +) def to_code(config): diff --git a/esphome/components/sx1509/output/__init__.py b/esphome/components/sx1509/output/__init__.py index 80aec0afd4f9..878534e82959 100644 --- a/esphome/components/sx1509/output/__init__.py +++ b/esphome/components/sx1509/output/__init__.py @@ -4,16 +4,19 @@ from esphome.const import CONF_PIN, CONF_ID from .. import SX1509Component, sx1509_ns, CONF_SX1509_ID -DEPENDENCIES = ['sx1509'] +DEPENDENCIES = ["sx1509"] -SX1509FloatOutputChannel = sx1509_ns.class_('SX1509FloatOutputChannel', - output.FloatOutput, cg.Component) +SX1509FloatOutputChannel = sx1509_ns.class_( + "SX1509FloatOutputChannel", output.FloatOutput, cg.Component +) -CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ - cv.Required(CONF_ID): cv.declare_id(SX1509FloatOutputChannel), - cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component), - cv.Required(CONF_PIN): cv.int_range(min=0, max=15), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(SX1509FloatOutputChannel), + cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component), + cv.Required(CONF_PIN): cv.int_range(min=0, max=15), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/tcl112/climate.py b/esphome/components/tcl112/climate.py index 11ebdc7be889..587bdcfbe91e 100644 --- a/esphome/components/tcl112/climate.py +++ b/esphome/components/tcl112/climate.py @@ -3,15 +3,17 @@ from esphome.components import climate_ir from esphome.const import CONF_ID -AUTO_LOAD = ['climate_ir'] -CODEOWNERS = ['@glmnet'] +AUTO_LOAD = ["climate_ir"] +CODEOWNERS = ["@glmnet"] -tcl112_ns = cg.esphome_ns.namespace('tcl112') -Tcl112Climate = tcl112_ns.class_('Tcl112Climate', climate_ir.ClimateIR) +tcl112_ns = cg.esphome_ns.namespace("tcl112") +Tcl112Climate = tcl112_ns.class_("Tcl112Climate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(Tcl112Climate), -}) +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Tcl112Climate), + } +) def to_code(config): diff --git a/esphome/components/tcs34725/sensor.py b/esphome/components/tcs34725/sensor.py index 287b2e441c2d..84e3c290bf87 100644 --- a/esphome/components/tcs34725/sensor.py +++ b/esphome/components/tcs34725/sensor.py @@ -1,54 +1,81 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_COLOR_TEMPERATURE, CONF_GAIN, CONF_ID, \ - CONF_ILLUMINANCE, CONF_INTEGRATION_TIME, ICON_LIGHTBULB, \ - UNIT_PERCENT, ICON_THERMOMETER, UNIT_KELVIN, ICON_BRIGHTNESS_5, UNIT_LUX +from esphome.const import ( + CONF_COLOR_TEMPERATURE, + CONF_GAIN, + CONF_ID, + CONF_ILLUMINANCE, + CONF_INTEGRATION_TIME, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ILLUMINANCE, + ICON_EMPTY, + ICON_LIGHTBULB, + UNIT_PERCENT, + ICON_THERMOMETER, + UNIT_KELVIN, + UNIT_LUX, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -CONF_RED_CHANNEL = 'red_channel' -CONF_GREEN_CHANNEL = 'green_channel' -CONF_BLUE_CHANNEL = 'blue_channel' -CONF_CLEAR_CHANNEL = 'clear_channel' +CONF_RED_CHANNEL = "red_channel" +CONF_GREEN_CHANNEL = "green_channel" +CONF_BLUE_CHANNEL = "blue_channel" +CONF_CLEAR_CHANNEL = "clear_channel" -tcs34725_ns = cg.esphome_ns.namespace('tcs34725') -TCS34725Component = tcs34725_ns.class_('TCS34725Component', cg.PollingComponent, i2c.I2CDevice) +tcs34725_ns = cg.esphome_ns.namespace("tcs34725") +TCS34725Component = tcs34725_ns.class_( + "TCS34725Component", cg.PollingComponent, i2c.I2CDevice +) -TCS34725IntegrationTime = tcs34725_ns.enum('TCS34725IntegrationTime') +TCS34725IntegrationTime = tcs34725_ns.enum("TCS34725IntegrationTime") TCS34725_INTEGRATION_TIMES = { - '2.4ms': TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_2_4MS, - '24ms': TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_24MS, - '50ms': TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_50MS, - '101ms': TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_101MS, - '154ms': TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_154MS, - '700ms': TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_700MS, + "2.4ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_2_4MS, + "24ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_24MS, + "50ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_50MS, + "101ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_101MS, + "154ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_154MS, + "700ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_700MS, } -TCS34725Gain = tcs34725_ns.enum('TCS34725Gain') +TCS34725Gain = tcs34725_ns.enum("TCS34725Gain") TCS34725_GAINS = { - '1X': TCS34725Gain.TCS34725_GAIN_1X, - '4X': TCS34725Gain.TCS34725_GAIN_4X, - '16X': TCS34725Gain.TCS34725_GAIN_16X, - '60X': TCS34725Gain.TCS34725_GAIN_60X, + "1X": TCS34725Gain.TCS34725_GAIN_1X, + "4X": TCS34725Gain.TCS34725_GAIN_4X, + "16X": TCS34725Gain.TCS34725_GAIN_16X, + "60X": TCS34725Gain.TCS34725_GAIN_60X, } -color_channel_schema = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1) -color_temperature_schema = sensor.sensor_schema(UNIT_KELVIN, ICON_THERMOMETER, 1) -illuminance_schema = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1) +color_channel_schema = sensor.sensor_schema( + UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY +) +color_temperature_schema = sensor.sensor_schema( + UNIT_KELVIN, ICON_THERMOMETER, 1, DEVICE_CLASS_EMPTY +) +illuminance_schema = sensor.sensor_schema( + UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(TCS34725Component), - cv.Optional(CONF_RED_CHANNEL): color_channel_schema, - cv.Optional(CONF_GREEN_CHANNEL): color_channel_schema, - cv.Optional(CONF_BLUE_CHANNEL): color_channel_schema, - cv.Optional(CONF_CLEAR_CHANNEL): color_channel_schema, - cv.Optional(CONF_ILLUMINANCE): illuminance_schema, - cv.Optional(CONF_COLOR_TEMPERATURE): color_temperature_schema, - cv.Optional(CONF_INTEGRATION_TIME, default='2.4ms'): - cv.enum(TCS34725_INTEGRATION_TIMES, lower=True), - cv.Optional(CONF_GAIN, default='1X'): cv.enum(TCS34725_GAINS, upper=True), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x29)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TCS34725Component), + cv.Optional(CONF_RED_CHANNEL): color_channel_schema, + cv.Optional(CONF_GREEN_CHANNEL): color_channel_schema, + cv.Optional(CONF_BLUE_CHANNEL): color_channel_schema, + cv.Optional(CONF_CLEAR_CHANNEL): color_channel_schema, + cv.Optional(CONF_ILLUMINANCE): illuminance_schema, + cv.Optional(CONF_COLOR_TEMPERATURE): color_temperature_schema, + cv.Optional(CONF_INTEGRATION_TIME, default="2.4ms"): cv.enum( + TCS34725_INTEGRATION_TIMES, lower=True + ), + cv.Optional(CONF_GAIN, default="1X"): cv.enum(TCS34725_GAINS, upper=True), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x29)) +) def to_code(config): diff --git a/esphome/components/teleinfo/__init__.py b/esphome/components/teleinfo/__init__.py index 00ca59227253..2e279e892e77 100644 --- a/esphome/components/teleinfo/__init__.py +++ b/esphome/components/teleinfo/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@0hax'] +CODEOWNERS = ["@0hax"] diff --git a/esphome/components/teleinfo/sensor.py b/esphome/components/teleinfo/sensor.py index 54b50a992132..9f6c2c8e89f1 100644 --- a/esphome/components/teleinfo/sensor.py +++ b/esphome/components/teleinfo/sensor.py @@ -1,26 +1,42 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, uart -from esphome.const import CONF_ID, CONF_SENSOR, ICON_FLASH, UNIT_WATT_HOURS +from esphome.const import ( + CONF_ID, + CONF_SENSOR, + DEVICE_CLASS_POWER, + ICON_EMPTY, + UNIT_WATT_HOURS, +) -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] -teleinfo_ns = cg.esphome_ns.namespace('teleinfo') -TeleInfo = teleinfo_ns.class_('TeleInfo', cg.PollingComponent, uart.UARTDevice) +teleinfo_ns = cg.esphome_ns.namespace("teleinfo") +TeleInfo = teleinfo_ns.class_("TeleInfo", cg.PollingComponent, uart.UARTDevice) CONF_TAG_NAME = "tag_name" -TELEINFO_TAG_SCHEMA = cv.Schema({ - cv.Required(CONF_TAG_NAME): cv.string, - cv.Required(CONF_SENSOR): sensor.sensor_schema(UNIT_WATT_HOURS, ICON_FLASH, 0) -}) +TELEINFO_TAG_SCHEMA = cv.Schema( + { + cv.Required(CONF_TAG_NAME): cv.string, + cv.Required(CONF_SENSOR): sensor.sensor_schema( + UNIT_WATT_HOURS, ICON_EMPTY, 0, DEVICE_CLASS_POWER + ), + } +) CONF_TAGS = "tags" CONF_HISTORICAL_MODE = "historical_mode" -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(TeleInfo), - cv.Optional(CONF_HISTORICAL_MODE, default=False): cv.boolean, - cv.Optional(CONF_TAGS): cv.ensure_list(TELEINFO_TAG_SCHEMA), -}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TeleInfo), + cv.Optional(CONF_HISTORICAL_MODE, default=False): cv.boolean, + cv.Optional(CONF_TAGS): cv.ensure_list(TELEINFO_TAG_SCHEMA), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): diff --git a/esphome/components/template/__init__.py b/esphome/components/template/__init__.py index 44c59260d3f6..6253af9090f6 100644 --- a/esphome/components/template/__init__.py +++ b/esphome/components/template/__init__.py @@ -1,3 +1,3 @@ import esphome.codegen as cg -template_ns = cg.esphome_ns.namespace('template_') +template_ns = cg.esphome_ns.namespace("template_") diff --git a/esphome/components/template/binary_sensor/__init__.py b/esphome/components/template/binary_sensor/__init__.py index 14f9f23ec2fe..d23e6423a120 100644 --- a/esphome/components/template/binary_sensor/__init__.py +++ b/esphome/components/template/binary_sensor/__init__.py @@ -5,13 +5,16 @@ from esphome.const import CONF_ID, CONF_LAMBDA, CONF_STATE from .. import template_ns -TemplateBinarySensor = template_ns.class_('TemplateBinarySensor', binary_sensor.BinarySensor, - cg.Component) +TemplateBinarySensor = template_ns.class_( + "TemplateBinarySensor", binary_sensor.BinarySensor, cg.Component +) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TemplateBinarySensor), - cv.Optional(CONF_LAMBDA): cv.returning_lambda, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateBinarySensor), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -20,17 +23,22 @@ def to_code(config): yield binary_sensor.register_binary_sensor(var, config) if CONF_LAMBDA in config: - template_ = yield cg.process_lambda(config[CONF_LAMBDA], [], - return_type=cg.optional.template(bool)) + template_ = yield cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(bool) + ) cg.add(var.set_template(template_)) -@automation.register_action('binary_sensor.template.publish', - binary_sensor.BinarySensorPublishAction, - cv.Schema({ - cv.Required(CONF_ID): cv.use_id(binary_sensor.BinarySensor), - cv.Required(CONF_STATE): cv.templatable(cv.boolean), - })) +@automation.register_action( + "binary_sensor.template.publish", + binary_sensor.BinarySensorPublishAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(binary_sensor.BinarySensor), + cv.Required(CONF_STATE): cv.templatable(cv.boolean), + } + ), +) def binary_sensor_template_publish_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/template/cover/__init__.py b/esphome/components/template/cover/__init__.py index 26ad5044665f..14050cb7c675 100644 --- a/esphome/components/template/cover/__init__.py +++ b/esphome/components/template/cover/__init__.py @@ -2,37 +2,54 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import cover -from esphome.const import CONF_ASSUMED_STATE, CONF_CLOSE_ACTION, CONF_CURRENT_OPERATION, CONF_ID, \ - CONF_LAMBDA, CONF_OPEN_ACTION, CONF_OPTIMISTIC, CONF_POSITION, CONF_RESTORE_MODE, \ - CONF_STATE, CONF_STOP_ACTION, CONF_TILT, CONF_TILT_ACTION, CONF_TILT_LAMBDA, \ - CONF_POSITION_ACTION +from esphome.const import ( + CONF_ASSUMED_STATE, + CONF_CLOSE_ACTION, + CONF_CURRENT_OPERATION, + CONF_ID, + CONF_LAMBDA, + CONF_OPEN_ACTION, + CONF_OPTIMISTIC, + CONF_POSITION, + CONF_RESTORE_MODE, + CONF_STATE, + CONF_STOP_ACTION, + CONF_TILT, + CONF_TILT_ACTION, + CONF_TILT_LAMBDA, + CONF_POSITION_ACTION, +) from .. import template_ns -TemplateCover = template_ns.class_('TemplateCover', cover.Cover, cg.Component) +TemplateCover = template_ns.class_("TemplateCover", cover.Cover, cg.Component) -TemplateCoverRestoreMode = template_ns.enum('TemplateCoverRestoreMode') +TemplateCoverRestoreMode = template_ns.enum("TemplateCoverRestoreMode") RESTORE_MODES = { - 'NO_RESTORE': TemplateCoverRestoreMode.COVER_NO_RESTORE, - 'RESTORE': TemplateCoverRestoreMode.COVER_RESTORE, - 'RESTORE_AND_CALL': TemplateCoverRestoreMode.COVER_RESTORE_AND_CALL, + "NO_RESTORE": TemplateCoverRestoreMode.COVER_NO_RESTORE, + "RESTORE": TemplateCoverRestoreMode.COVER_RESTORE, + "RESTORE_AND_CALL": TemplateCoverRestoreMode.COVER_RESTORE_AND_CALL, } -CONF_HAS_POSITION = 'has_position' +CONF_HAS_POSITION = "has_position" -CONFIG_SCHEMA = cover.COVER_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TemplateCover), - 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_HAS_POSITION, default=False): cv.boolean, - cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_TILT_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_TILT_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_POSITION_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_RESTORE_MODE, default='RESTORE'): cv.enum(RESTORE_MODES, upper=True), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateCover), + 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_HAS_POSITION, default=False): cv.boolean, + cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_TILT_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_TILT_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_POSITION_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_RESTORE_MODE, default="RESTORE"): cv.enum( + RESTORE_MODES, upper=True + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -40,26 +57,36 @@ def to_code(config): yield cg.register_component(var, config) yield cover.register_cover(var, config) if CONF_LAMBDA in config: - template_ = yield cg.process_lambda(config[CONF_LAMBDA], [], - return_type=cg.optional.template(float)) + template_ = yield cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(float) + ) cg.add(var.set_state_lambda(template_)) if CONF_OPEN_ACTION in config: - yield automation.build_automation(var.get_open_trigger(), [], config[CONF_OPEN_ACTION]) + yield automation.build_automation( + var.get_open_trigger(), [], config[CONF_OPEN_ACTION] + ) if CONF_CLOSE_ACTION in config: - yield automation.build_automation(var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]) + yield automation.build_automation( + var.get_close_trigger(), [], config[CONF_CLOSE_ACTION] + ) if CONF_STOP_ACTION in config: - yield automation.build_automation(var.get_stop_trigger(), [], config[CONF_STOP_ACTION]) + yield automation.build_automation( + var.get_stop_trigger(), [], config[CONF_STOP_ACTION] + ) if CONF_TILT_ACTION in config: - yield automation.build_automation(var.get_tilt_trigger(), [(float, 'tilt')], - config[CONF_TILT_ACTION]) + yield automation.build_automation( + var.get_tilt_trigger(), [(float, "tilt")], config[CONF_TILT_ACTION] + ) cg.add(var.set_has_tilt(True)) if CONF_TILT_LAMBDA in config: - tilt_template_ = yield cg.process_lambda(config[CONF_TILT_LAMBDA], [], - return_type=cg.optional.template(float)) + tilt_template_ = yield cg.process_lambda( + config[CONF_TILT_LAMBDA], [], return_type=cg.optional.template(float) + ) cg.add(var.set_tilt_lambda(tilt_template_)) if CONF_POSITION_ACTION in config: - yield automation.build_automation(var.get_position_trigger(), [(float, 'pos')], - config[CONF_POSITION_ACTION]) + yield automation.build_automation( + var.get_position_trigger(), [(float, "pos")], config[CONF_POSITION_ACTION] + ) cg.add(var.set_has_position(True)) else: cg.add(var.set_has_position(config[CONF_HAS_POSITION])) @@ -69,13 +96,21 @@ def to_code(config): cg.add(var.set_has_position(config[CONF_HAS_POSITION])) -@automation.register_action('cover.template.publish', cover.CoverPublishAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(cover.Cover), - cv.Exclusive(CONF_STATE, 'pos'): cv.templatable(cover.validate_cover_state), - cv.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.zero_to_one_float), - cv.Optional(CONF_CURRENT_OPERATION): cv.templatable(cover.validate_cover_operation), - cv.Optional(CONF_TILT): cv.templatable(cv.zero_to_one_float), -})) +@automation.register_action( + "cover.template.publish", + cover.CoverPublishAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(cover.Cover), + cv.Exclusive(CONF_STATE, "pos"): cv.templatable(cover.validate_cover_state), + cv.Exclusive(CONF_POSITION, "pos"): cv.templatable(cv.zero_to_one_float), + cv.Optional(CONF_CURRENT_OPERATION): cv.templatable( + cover.validate_cover_operation + ), + cv.Optional(CONF_TILT): cv.templatable(cv.zero_to_one_float), + } + ), +) def cover_template_publish_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) @@ -89,6 +124,8 @@ def cover_template_publish_to_code(config, action_id, template_arg, args): template_ = yield cg.templatable(config[CONF_TILT], args, float) cg.add(var.set_tilt(template_)) if CONF_CURRENT_OPERATION in config: - template_ = yield cg.templatable(config[CONF_CURRENT_OPERATION], args, cover.CoverOperation) + template_ = yield cg.templatable( + config[CONF_CURRENT_OPERATION], args, cover.CoverOperation + ) cg.add(var.set_current_operation(template_)) yield var diff --git a/esphome/components/template/output/__init__.py b/esphome/components/template/output/__init__.py index cc85a9da6882..61286772d255 100644 --- a/esphome/components/template/output/__init__.py +++ b/esphome/components/template/output/__init__.py @@ -5,30 +5,43 @@ from esphome.const import CONF_ID, CONF_TYPE, CONF_BINARY from .. import template_ns -TemplateBinaryOutput = template_ns.class_('TemplateBinaryOutput', output.BinaryOutput) -TemplateFloatOutput = template_ns.class_('TemplateFloatOutput', output.FloatOutput) +TemplateBinaryOutput = template_ns.class_("TemplateBinaryOutput", output.BinaryOutput) +TemplateFloatOutput = template_ns.class_("TemplateFloatOutput", output.FloatOutput) -CONF_FLOAT = 'float' -CONF_WRITE_ACTION = 'write_action' +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) +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]) + 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 automation.build_automation( + var.get_trigger(), [(float, "state")], config[CONF_WRITE_ACTION] + ) yield output.register_output(var, config) diff --git a/esphome/components/template/sensor/__init__.py b/esphome/components/template/sensor/__init__.py index 3fc71cf9dec7..21ef5db32d71 100644 --- a/esphome/components/template/sensor/__init__.py +++ b/esphome/components/template/sensor/__init__.py @@ -2,15 +2,30 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import sensor -from esphome.const import CONF_ID, CONF_LAMBDA, CONF_STATE, UNIT_EMPTY, ICON_EMPTY +from esphome.const import ( + CONF_ID, + CONF_LAMBDA, + CONF_STATE, + DEVICE_CLASS_EMPTY, + UNIT_EMPTY, + ICON_EMPTY, +) from .. import template_ns -TemplateSensor = template_ns.class_('TemplateSensor', sensor.Sensor, cg.PollingComponent) +TemplateSensor = template_ns.class_( + "TemplateSensor", sensor.Sensor, cg.PollingComponent +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1).extend({ - cv.GenerateID(): cv.declare_id(TemplateSensor), - cv.Optional(CONF_LAMBDA): cv.returning_lambda, -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(TemplateSensor), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): @@ -19,16 +34,22 @@ def to_code(config): yield sensor.register_sensor(var, config) if CONF_LAMBDA in config: - template_ = yield cg.process_lambda(config[CONF_LAMBDA], [], - return_type=cg.optional.template(float)) + template_ = yield cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(float) + ) cg.add(var.set_template(template_)) -@automation.register_action('sensor.template.publish', sensor.SensorPublishAction, - cv.Schema({ - cv.Required(CONF_ID): cv.use_id(sensor.Sensor), - cv.Required(CONF_STATE): cv.templatable(cv.float_), - })) +@automation.register_action( + "sensor.template.publish", + sensor.SensorPublishAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(sensor.Sensor), + cv.Required(CONF_STATE): cv.templatable(cv.float_), + } + ), +) def sensor_template_publish_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/template/switch/__init__.py b/esphome/components/template/switch/__init__.py index 783f5a192236..7698e4f2a958 100644 --- a/esphome/components/template/switch/__init__.py +++ b/esphome/components/template/switch/__init__.py @@ -2,21 +2,31 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import switch -from esphome.const import CONF_ASSUMED_STATE, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC, \ - CONF_RESTORE_STATE, CONF_STATE, CONF_TURN_OFF_ACTION, CONF_TURN_ON_ACTION +from esphome.const import ( + CONF_ASSUMED_STATE, + CONF_ID, + CONF_LAMBDA, + CONF_OPTIMISTIC, + CONF_RESTORE_STATE, + CONF_STATE, + CONF_TURN_OFF_ACTION, + CONF_TURN_ON_ACTION, +) from .. import template_ns -TemplateSwitch = template_ns.class_('TemplateSwitch', switch.Switch, cg.Component) +TemplateSwitch = template_ns.class_("TemplateSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TemplateSwitch), - 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), - cv.Optional(CONF_TURN_ON_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_RESTORE_STATE, default=False): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateSwitch), + 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), + cv.Optional(CONF_TURN_ON_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_RESTORE_STATE, default=False): cv.boolean, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -25,24 +35,33 @@ def to_code(config): yield switch.register_switch(var, config) if CONF_LAMBDA in config: - template_ = yield cg.process_lambda(config[CONF_LAMBDA], [], - return_type=cg.optional.template(bool)) + template_ = yield cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(bool) + ) cg.add(var.set_state_lambda(template_)) if CONF_TURN_OFF_ACTION in config: - yield automation.build_automation(var.get_turn_off_trigger(), [], - config[CONF_TURN_OFF_ACTION]) + yield automation.build_automation( + var.get_turn_off_trigger(), [], config[CONF_TURN_OFF_ACTION] + ) if CONF_TURN_ON_ACTION in config: - yield automation.build_automation(var.get_turn_on_trigger(), [], - config[CONF_TURN_ON_ACTION]) + yield automation.build_automation( + var.get_turn_on_trigger(), [], config[CONF_TURN_ON_ACTION] + ) cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) cg.add(var.set_assumed_state(config[CONF_ASSUMED_STATE])) cg.add(var.set_restore_state(config[CONF_RESTORE_STATE])) -@automation.register_action('switch.template.publish', switch.SwitchPublishAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(switch.Switch), - cv.Required(CONF_STATE): cv.templatable(cv.boolean), -})) +@automation.register_action( + "switch.template.publish", + switch.SwitchPublishAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(switch.Switch), + cv.Required(CONF_STATE): cv.templatable(cv.boolean), + } + ), +) def switch_template_publish_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/template/text_sensor/__init__.py b/esphome/components/template/text_sensor/__init__.py index dae99dc9bc9a..cae00e39323d 100644 --- a/esphome/components/template/text_sensor/__init__.py +++ b/esphome/components/template/text_sensor/__init__.py @@ -6,13 +6,16 @@ from esphome.const import CONF_ID, CONF_LAMBDA, CONF_STATE from .. import template_ns -TemplateTextSensor = template_ns.class_('TemplateTextSensor', text_sensor.TextSensor, - cg.PollingComponent) +TemplateTextSensor = template_ns.class_( + "TemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent +) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TemplateTextSensor), - cv.Optional(CONF_LAMBDA): cv.returning_lambda, -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateTextSensor), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } +).extend(cv.polling_component_schema("60s")) def to_code(config): @@ -21,15 +24,22 @@ def to_code(config): yield text_sensor.register_text_sensor(var, config) if CONF_LAMBDA in config: - template_ = yield cg.process_lambda(config[CONF_LAMBDA], [], - return_type=cg.optional.template(cg.std_string)) + template_ = yield cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) + ) cg.add(var.set_template(template_)) -@automation.register_action('text_sensor.template.publish', TextSensorPublishAction, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(text_sensor.TextSensor), - cv.Required(CONF_STATE): cv.templatable(cv.string_strict), -})) +@automation.register_action( + "text_sensor.template.publish", + TextSensorPublishAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(text_sensor.TextSensor), + cv.Required(CONF_STATE): cv.templatable(cv.string_strict), + } + ), +) def text_sensor_template_publish_to_code(config, action_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index f138f38d2f69..ff73889d612f 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -2,31 +2,48 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt -from esphome.const import CONF_ICON, CONF_ID, CONF_INTERNAL, CONF_ON_VALUE, \ - CONF_TRIGGER_ID, CONF_MQTT_ID, CONF_NAME, CONF_STATE +from esphome.const import ( + CONF_ICON, + CONF_ID, + CONF_INTERNAL, + CONF_ON_VALUE, + CONF_TRIGGER_ID, + CONF_MQTT_ID, + CONF_NAME, + CONF_STATE, +) from esphome.core import CORE, coroutine, coroutine_with_priority IS_PLATFORM_COMPONENT = True # pylint: disable=invalid-name -text_sensor_ns = cg.esphome_ns.namespace('text_sensor') -TextSensor = text_sensor_ns.class_('TextSensor', cg.Nameable) -TextSensorPtr = TextSensor.operator('ptr') +text_sensor_ns = cg.esphome_ns.namespace("text_sensor") +TextSensor = text_sensor_ns.class_("TextSensor", cg.Nameable) +TextSensorPtr = TextSensor.operator("ptr") -TextSensorStateTrigger = text_sensor_ns.class_('TextSensorStateTrigger', - automation.Trigger.template(cg.std_string)) -TextSensorPublishAction = text_sensor_ns.class_('TextSensorPublishAction', automation.Action) -TextSensorStateCondition = text_sensor_ns.class_('TextSensorStateCondition', automation.Condition) +TextSensorStateTrigger = text_sensor_ns.class_( + "TextSensorStateTrigger", automation.Trigger.template(cg.std_string) +) +TextSensorPublishAction = text_sensor_ns.class_( + "TextSensorPublishAction", automation.Action +) +TextSensorStateCondition = text_sensor_ns.class_( + "TextSensorStateCondition", automation.Condition +) icon = cv.icon -TEXT_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ - cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTTextSensor), - cv.Optional(CONF_ICON): icon, - cv.Optional(CONF_ON_VALUE): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextSensorStateTrigger), - }), -}) +TEXT_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), + cv.Optional(CONF_ICON): icon, + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextSensorStateTrigger), + } + ), + } +) @coroutine @@ -39,7 +56,7 @@ def setup_text_sensor_core_(var, config): for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - yield automation.build_automation(trigger, [(cg.std_string, 'x')], conf) + yield automation.build_automation(trigger, [(cg.std_string, "x")], conf) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) @@ -56,14 +73,20 @@ def register_text_sensor(var, config): @coroutine_with_priority(100.0) def to_code(config): - cg.add_define('USE_TEXT_SENSOR') + cg.add_define("USE_TEXT_SENSOR") cg.add_global(text_sensor_ns.using) -@automation.register_condition('text_sensor.state', TextSensorStateCondition, cv.Schema({ - cv.Required(CONF_ID): cv.use_id(TextSensor), - cv.Required(CONF_STATE): cv.templatable(cv.string_strict), -})) +@automation.register_condition( + "text_sensor.state", + TextSensorStateCondition, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(TextSensor), + cv.Required(CONF_STATE): cv.templatable(cv.string_strict), + } + ), +) def text_sensor_state_to_code(config, condition_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(condition_id, template_arg, paren) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index bd8633ee1c59..04584583af65 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -2,109 +2,224 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import climate, sensor -from esphome.const import CONF_AUTO_MODE, CONF_AWAY_CONFIG, CONF_COOL_ACTION, CONF_COOL_MODE, \ - CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_DRY_ACTION, \ - CONF_DRY_MODE, CONF_FAN_MODE_ON_ACTION, CONF_FAN_MODE_OFF_ACTION, CONF_FAN_MODE_AUTO_ACTION, \ - CONF_FAN_MODE_LOW_ACTION, CONF_FAN_MODE_MEDIUM_ACTION, CONF_FAN_MODE_HIGH_ACTION, \ - CONF_FAN_MODE_MIDDLE_ACTION, CONF_FAN_MODE_FOCUS_ACTION, CONF_FAN_MODE_DIFFUSE_ACTION, \ - CONF_FAN_ONLY_ACTION, CONF_FAN_ONLY_MODE, CONF_HEAT_ACTION, CONF_HEAT_MODE, CONF_HYSTERESIS, \ - CONF_ID, CONF_IDLE_ACTION, CONF_OFF_MODE, CONF_SENSOR, CONF_SWING_BOTH_ACTION, \ - CONF_SWING_HORIZONTAL_ACTION, CONF_SWING_OFF_ACTION, CONF_SWING_VERTICAL_ACTION +from esphome.const import ( + CONF_AUTO_MODE, + CONF_AWAY_CONFIG, + CONF_COOL_ACTION, + CONF_COOL_MODE, + CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, + CONF_DEFAULT_TARGET_TEMPERATURE_LOW, + CONF_DRY_ACTION, + CONF_DRY_MODE, + CONF_FAN_MODE_ON_ACTION, + CONF_FAN_MODE_OFF_ACTION, + CONF_FAN_MODE_AUTO_ACTION, + CONF_FAN_MODE_LOW_ACTION, + CONF_FAN_MODE_MEDIUM_ACTION, + CONF_FAN_MODE_HIGH_ACTION, + CONF_FAN_MODE_MIDDLE_ACTION, + CONF_FAN_MODE_FOCUS_ACTION, + CONF_FAN_MODE_DIFFUSE_ACTION, + CONF_FAN_ONLY_ACTION, + CONF_FAN_ONLY_MODE, + CONF_HEAT_ACTION, + CONF_HEAT_MODE, + CONF_HYSTERESIS, + CONF_ID, + CONF_IDLE_ACTION, + CONF_OFF_MODE, + CONF_SENSOR, + CONF_SWING_BOTH_ACTION, + CONF_SWING_HORIZONTAL_ACTION, + CONF_SWING_OFF_ACTION, + CONF_SWING_VERTICAL_ACTION, +) -CODEOWNERS = ['@kbx81'] +CODEOWNERS = ["@kbx81"] -thermostat_ns = cg.esphome_ns.namespace('thermostat') -ThermostatClimate = thermostat_ns.class_('ThermostatClimate', climate.Climate, cg.Component) -ThermostatClimateTargetTempConfig = thermostat_ns.struct('ThermostatClimateTargetTempConfig') +thermostat_ns = cg.esphome_ns.namespace("thermostat") +ThermostatClimate = thermostat_ns.class_( + "ThermostatClimate", climate.Climate, cg.Component +) +ThermostatClimateTargetTempConfig = thermostat_ns.struct( + "ThermostatClimateTargetTempConfig" +) def validate_thermostat(config): # verify corresponding climate action action exists for any defined climate mode action if CONF_COOL_MODE in config and CONF_COOL_ACTION not in config: - raise cv.Invalid("{} must be defined to use {}".format(CONF_COOL_ACTION, CONF_COOL_MODE)) + raise cv.Invalid( + "{} must be defined to use {}".format(CONF_COOL_ACTION, CONF_COOL_MODE) + ) if CONF_DRY_MODE in config and CONF_DRY_ACTION not in config: - raise cv.Invalid("{} must be defined to use {}".format(CONF_DRY_ACTION, CONF_DRY_MODE)) + raise cv.Invalid( + "{} must be defined to use {}".format(CONF_DRY_ACTION, CONF_DRY_MODE) + ) if CONF_FAN_ONLY_MODE in config and CONF_FAN_ONLY_ACTION not in config: - raise cv.Invalid("{} must be defined to use {}".format(CONF_FAN_ONLY_ACTION, - CONF_FAN_ONLY_MODE)) + raise cv.Invalid( + "{} must be defined to use {}".format( + CONF_FAN_ONLY_ACTION, CONF_FAN_ONLY_MODE + ) + ) if CONF_HEAT_MODE in config and CONF_HEAT_ACTION not in config: - raise cv.Invalid("{} must be defined to use {}".format(CONF_HEAT_ACTION, CONF_HEAT_MODE)) + raise cv.Invalid( + "{} must be defined to use {}".format(CONF_HEAT_ACTION, CONF_HEAT_MODE) + ) # verify corresponding default target temperature exists when a given climate action exists - if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH not in config and (CONF_COOL_ACTION in config - or CONF_FAN_ONLY_ACTION in config): - raise cv.Invalid("{} must be defined when using {} or {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_COOL_ACTION, CONF_FAN_ONLY_ACTION)) + if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH not in config and ( + CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config + ): + raise cv.Invalid( + "{} must be defined when using {} or {}".format( + CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, + CONF_COOL_ACTION, + CONF_FAN_ONLY_ACTION, + ) + ) if CONF_DEFAULT_TARGET_TEMPERATURE_LOW not in config and CONF_HEAT_ACTION in config: - raise cv.Invalid("{} must be defined when using {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION)) + raise cv.Invalid( + "{} must be defined when using {}".format( + CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION + ) + ) # if a given climate action is NOT defined, it should not have a default target temperature - if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config and (CONF_COOL_ACTION not in config - and CONF_FAN_ONLY_ACTION not in config): - raise cv.Invalid("{} is defined with no {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_COOL_ACTION)) + if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config and ( + CONF_COOL_ACTION not in config and CONF_FAN_ONLY_ACTION not in config + ): + raise cv.Invalid( + "{} is defined with no {}".format( + CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_COOL_ACTION + ) + ) if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config and CONF_HEAT_ACTION not in config: - raise cv.Invalid("{} is defined with no {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION)) + raise cv.Invalid( + "{} is defined with no {}".format( + CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION + ) + ) if CONF_AWAY_CONFIG in config: away = config[CONF_AWAY_CONFIG] # verify corresponding default target temperature exists when a given climate action exists - if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH not in away and (CONF_COOL_ACTION in config or - CONF_FAN_ONLY_ACTION in config): - raise cv.Invalid("{} must be defined in away configuration when using {} or {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_COOL_ACTION, CONF_FAN_ONLY_ACTION)) - if CONF_DEFAULT_TARGET_TEMPERATURE_LOW not in away and CONF_HEAT_ACTION in config: - raise cv.Invalid("{} must be defined in away configuration when using {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION)) + if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH not in away and ( + CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config + ): + raise cv.Invalid( + "{} must be defined in away configuration when using {} or {}".format( + CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, + CONF_COOL_ACTION, + CONF_FAN_ONLY_ACTION, + ) + ) + if ( + CONF_DEFAULT_TARGET_TEMPERATURE_LOW not in away + and CONF_HEAT_ACTION in config + ): + raise cv.Invalid( + "{} must be defined in away configuration when using {}".format( + CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION + ) + ) # if a given climate action is NOT defined, it should not have a default target temperature - if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in away and (CONF_COOL_ACTION not in config and - CONF_FAN_ONLY_ACTION not in config): - raise cv.Invalid("{} is defined in away configuration with no {} or {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_COOL_ACTION, CONF_FAN_ONLY_ACTION)) - if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in away and CONF_HEAT_ACTION not in config: - raise cv.Invalid("{} is defined in away configuration with no {}".format( - CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION)) + if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in away and ( + CONF_COOL_ACTION not in config and CONF_FAN_ONLY_ACTION not in config + ): + raise cv.Invalid( + "{} is defined in away configuration with no {} or {}".format( + CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, + CONF_COOL_ACTION, + CONF_FAN_ONLY_ACTION, + ) + ) + if ( + CONF_DEFAULT_TARGET_TEMPERATURE_LOW in away + and CONF_HEAT_ACTION not in config + ): + raise cv.Invalid( + "{} is defined in away configuration with no {}".format( + CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION + ) + ) return config -CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(ThermostatClimate), - cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), - cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_DRY_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_ONLY_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_AUTO_MODE): automation.validate_automation(single=True), - cv.Optional(CONF_COOL_MODE): automation.validate_automation(single=True), - cv.Optional(CONF_DRY_MODE): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_ONLY_MODE): automation.validate_automation(single=True), - cv.Optional(CONF_HEAT_MODE): automation.validate_automation(single=True), - cv.Optional(CONF_OFF_MODE): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_MODE_ON_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_MODE_OFF_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_MODE_AUTO_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_MODE_LOW_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_MODE_MEDIUM_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_MODE_HIGH_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_MODE_MIDDLE_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_MODE_FOCUS_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_FAN_MODE_DIFFUSE_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_SWING_BOTH_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_SWING_HORIZONTAL_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_SWING_OFF_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_SWING_VERTICAL_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, - cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, - cv.Optional(CONF_HYSTERESIS, default=0.5): cv.temperature, - cv.Optional(CONF_AWAY_CONFIG): cv.Schema({ - cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, - cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, - }), -}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_DRY_ACTION, - CONF_FAN_ONLY_ACTION, CONF_HEAT_ACTION), - validate_thermostat) +CONFIG_SCHEMA = cv.All( + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ThermostatClimate), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_DRY_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_FAN_ONLY_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_AUTO_MODE): automation.validate_automation(single=True), + cv.Optional(CONF_COOL_MODE): automation.validate_automation(single=True), + cv.Optional(CONF_DRY_MODE): automation.validate_automation(single=True), + cv.Optional(CONF_FAN_ONLY_MODE): automation.validate_automation( + single=True + ), + cv.Optional(CONF_HEAT_MODE): automation.validate_automation(single=True), + cv.Optional(CONF_OFF_MODE): automation.validate_automation(single=True), + cv.Optional(CONF_FAN_MODE_ON_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_FAN_MODE_OFF_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_FAN_MODE_AUTO_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_FAN_MODE_LOW_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_FAN_MODE_MEDIUM_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_FAN_MODE_HIGH_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_FAN_MODE_MIDDLE_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_FAN_MODE_FOCUS_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_FAN_MODE_DIFFUSE_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_SWING_BOTH_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_SWING_HORIZONTAL_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_SWING_OFF_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_SWING_VERTICAL_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, + cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, + cv.Optional(CONF_HYSTERESIS, default=0.5): cv.temperature, + cv.Optional(CONF_AWAY_CONFIG): cv.Schema( + { + cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, + cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, + } + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.has_at_least_one_key( + CONF_COOL_ACTION, CONF_DRY_ACTION, CONF_FAN_ONLY_ACTION, CONF_HEAT_ACTION + ), + validate_thermostat, +) def to_code(config): @@ -113,8 +228,9 @@ def to_code(config): yield climate.register_climate(var, config) auto_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config - two_points_available = CONF_HEAT_ACTION in config and (CONF_COOL_ACTION in config or - CONF_FAN_ONLY_ACTION in config) + two_points_available = CONF_HEAT_ACTION in config and ( + CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config + ) sens = yield cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_sensor(sens)) @@ -124,7 +240,7 @@ def to_code(config): cg.add(var.set_supports_two_points(True)) normal_config = ThermostatClimateTargetTempConfig( config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], - config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] + config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], ) elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config: cg.add(var.set_supports_two_points(False)) @@ -138,8 +254,9 @@ def to_code(config): ) cg.add(var.set_normal_config(normal_config)) - yield automation.build_automation(var.get_idle_action_trigger(), [], - config[CONF_IDLE_ACTION]) + yield automation.build_automation( + var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION] + ) if auto_mode_available is True: cg.add(var.set_supports_auto(True)) @@ -147,94 +264,121 @@ def to_code(config): cg.add(var.set_supports_auto(False)) if CONF_COOL_ACTION in config: - yield automation.build_automation(var.get_cool_action_trigger(), [], - config[CONF_COOL_ACTION]) + yield automation.build_automation( + var.get_cool_action_trigger(), [], config[CONF_COOL_ACTION] + ) cg.add(var.set_supports_cool(True)) if CONF_DRY_ACTION in config: - yield automation.build_automation(var.get_dry_action_trigger(), [], - config[CONF_DRY_ACTION]) + yield automation.build_automation( + var.get_dry_action_trigger(), [], config[CONF_DRY_ACTION] + ) cg.add(var.set_supports_dry(True)) if CONF_FAN_ONLY_ACTION in config: - yield automation.build_automation(var.get_fan_only_action_trigger(), [], - config[CONF_FAN_ONLY_ACTION]) + yield automation.build_automation( + var.get_fan_only_action_trigger(), [], config[CONF_FAN_ONLY_ACTION] + ) cg.add(var.set_supports_fan_only(True)) if CONF_HEAT_ACTION in config: - yield automation.build_automation(var.get_heat_action_trigger(), [], - config[CONF_HEAT_ACTION]) + yield automation.build_automation( + var.get_heat_action_trigger(), [], config[CONF_HEAT_ACTION] + ) cg.add(var.set_supports_heat(True)) if CONF_AUTO_MODE in config: - yield automation.build_automation(var.get_auto_mode_trigger(), [], - config[CONF_AUTO_MODE]) + yield automation.build_automation( + var.get_auto_mode_trigger(), [], config[CONF_AUTO_MODE] + ) if CONF_COOL_MODE in config: - yield automation.build_automation(var.get_cool_mode_trigger(), [], - config[CONF_COOL_MODE]) + yield automation.build_automation( + var.get_cool_mode_trigger(), [], config[CONF_COOL_MODE] + ) cg.add(var.set_supports_cool(True)) if CONF_DRY_MODE in config: - yield automation.build_automation(var.get_dry_mode_trigger(), [], - config[CONF_DRY_MODE]) + yield automation.build_automation( + var.get_dry_mode_trigger(), [], config[CONF_DRY_MODE] + ) cg.add(var.set_supports_dry(True)) if CONF_FAN_ONLY_MODE in config: - yield automation.build_automation(var.get_fan_only_mode_trigger(), [], - config[CONF_FAN_ONLY_MODE]) + yield automation.build_automation( + var.get_fan_only_mode_trigger(), [], config[CONF_FAN_ONLY_MODE] + ) cg.add(var.set_supports_fan_only(True)) if CONF_HEAT_MODE in config: - yield automation.build_automation(var.get_heat_mode_trigger(), [], - config[CONF_HEAT_MODE]) + yield automation.build_automation( + var.get_heat_mode_trigger(), [], config[CONF_HEAT_MODE] + ) cg.add(var.set_supports_heat(True)) if CONF_OFF_MODE in config: - yield automation.build_automation(var.get_off_mode_trigger(), [], - config[CONF_OFF_MODE]) + yield automation.build_automation( + var.get_off_mode_trigger(), [], config[CONF_OFF_MODE] + ) if CONF_FAN_MODE_ON_ACTION in config: - yield automation.build_automation(var.get_fan_mode_on_trigger(), [], - config[CONF_FAN_MODE_ON_ACTION]) + yield automation.build_automation( + var.get_fan_mode_on_trigger(), [], config[CONF_FAN_MODE_ON_ACTION] + ) cg.add(var.set_supports_fan_mode_on(True)) if CONF_FAN_MODE_OFF_ACTION in config: - yield automation.build_automation(var.get_fan_mode_off_trigger(), [], - config[CONF_FAN_MODE_OFF_ACTION]) + yield automation.build_automation( + var.get_fan_mode_off_trigger(), [], config[CONF_FAN_MODE_OFF_ACTION] + ) cg.add(var.set_supports_fan_mode_off(True)) if CONF_FAN_MODE_AUTO_ACTION in config: - yield automation.build_automation(var.get_fan_mode_auto_trigger(), [], - config[CONF_FAN_MODE_AUTO_ACTION]) + yield automation.build_automation( + var.get_fan_mode_auto_trigger(), [], config[CONF_FAN_MODE_AUTO_ACTION] + ) cg.add(var.set_supports_fan_mode_auto(True)) if CONF_FAN_MODE_LOW_ACTION in config: - yield automation.build_automation(var.get_fan_mode_low_trigger(), [], - config[CONF_FAN_MODE_LOW_ACTION]) + yield automation.build_automation( + var.get_fan_mode_low_trigger(), [], config[CONF_FAN_MODE_LOW_ACTION] + ) cg.add(var.set_supports_fan_mode_low(True)) if CONF_FAN_MODE_MEDIUM_ACTION in config: - yield automation.build_automation(var.get_fan_mode_medium_trigger(), [], - config[CONF_FAN_MODE_MEDIUM_ACTION]) + yield automation.build_automation( + var.get_fan_mode_medium_trigger(), [], config[CONF_FAN_MODE_MEDIUM_ACTION] + ) cg.add(var.set_supports_fan_mode_medium(True)) if CONF_FAN_MODE_HIGH_ACTION in config: - yield automation.build_automation(var.get_fan_mode_high_trigger(), [], - config[CONF_FAN_MODE_HIGH_ACTION]) + yield automation.build_automation( + var.get_fan_mode_high_trigger(), [], config[CONF_FAN_MODE_HIGH_ACTION] + ) cg.add(var.set_supports_fan_mode_high(True)) if CONF_FAN_MODE_MIDDLE_ACTION in config: - yield automation.build_automation(var.get_fan_mode_middle_trigger(), [], - config[CONF_FAN_MODE_MIDDLE_ACTION]) + yield automation.build_automation( + var.get_fan_mode_middle_trigger(), [], config[CONF_FAN_MODE_MIDDLE_ACTION] + ) cg.add(var.set_supports_fan_mode_middle(True)) if CONF_FAN_MODE_FOCUS_ACTION in config: - yield automation.build_automation(var.get_fan_mode_focus_trigger(), [], - config[CONF_FAN_MODE_FOCUS_ACTION]) + yield automation.build_automation( + var.get_fan_mode_focus_trigger(), [], config[CONF_FAN_MODE_FOCUS_ACTION] + ) cg.add(var.set_supports_fan_mode_focus(True)) if CONF_FAN_MODE_DIFFUSE_ACTION in config: - yield automation.build_automation(var.get_fan_mode_diffuse_trigger(), [], - config[CONF_FAN_MODE_DIFFUSE_ACTION]) + yield automation.build_automation( + var.get_fan_mode_diffuse_trigger(), [], config[CONF_FAN_MODE_DIFFUSE_ACTION] + ) cg.add(var.set_supports_fan_mode_diffuse(True)) if CONF_SWING_BOTH_ACTION in config: - yield automation.build_automation(var.get_swing_mode_both_trigger(), [], - config[CONF_SWING_BOTH_ACTION]) + yield automation.build_automation( + var.get_swing_mode_both_trigger(), [], config[CONF_SWING_BOTH_ACTION] + ) cg.add(var.set_supports_swing_mode_both(True)) if CONF_SWING_HORIZONTAL_ACTION in config: - yield automation.build_automation(var.get_swing_mode_horizontal_trigger(), [], - config[CONF_SWING_HORIZONTAL_ACTION]) + yield automation.build_automation( + var.get_swing_mode_horizontal_trigger(), + [], + config[CONF_SWING_HORIZONTAL_ACTION], + ) cg.add(var.set_supports_swing_mode_horizontal(True)) if CONF_SWING_OFF_ACTION in config: - yield automation.build_automation(var.get_swing_mode_off_trigger(), [], - config[CONF_SWING_OFF_ACTION]) + yield automation.build_automation( + var.get_swing_mode_off_trigger(), [], config[CONF_SWING_OFF_ACTION] + ) cg.add(var.set_supports_swing_mode_off(True)) if CONF_SWING_VERTICAL_ACTION in config: - yield automation.build_automation(var.get_swing_mode_vertical_trigger(), [], - config[CONF_SWING_VERTICAL_ACTION]) + yield automation.build_automation( + var.get_swing_mode_vertical_trigger(), + [], + config[CONF_SWING_VERTICAL_ACTION], + ) cg.add(var.set_supports_swing_mode_vertical(True)) if CONF_AWAY_CONFIG in config: @@ -243,7 +387,7 @@ def to_code(config): if two_points_available is True: away_config = ThermostatClimateTargetTempConfig( away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], - away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] + away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], ) elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in away: away_config = ThermostatClimateTargetTempConfig( diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 5f30a8f2eeb6..b1c938c18e07 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -10,23 +10,38 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ID, CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \ - CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_ON_TIME_SYNC, CONF_SECONDS, CONF_TIMEZONE, \ - CONF_TRIGGER_ID, CONF_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE +from esphome.const import ( + CONF_ID, + CONF_CRON, + CONF_DAYS_OF_MONTH, + CONF_DAYS_OF_WEEK, + CONF_HOURS, + CONF_MINUTES, + CONF_MONTHS, + CONF_ON_TIME, + CONF_ON_TIME_SYNC, + CONF_SECONDS, + CONF_TIMEZONE, + CONF_TRIGGER_ID, + CONF_AT, + CONF_SECOND, + CONF_HOUR, + CONF_MINUTE, +) from esphome.core import coroutine, coroutine_with_priority from esphome.automation import Condition _LOGGER = logging.getLogger(__name__) -CODEOWNERS = ['@OttoWinter'] +CODEOWNERS = ["@OttoWinter"] IS_PLATFORM_COMPONENT = True -time_ns = cg.esphome_ns.namespace('time') -RealTimeClock = time_ns.class_('RealTimeClock', cg.PollingComponent) -CronTrigger = time_ns.class_('CronTrigger', automation.Trigger.template(), cg.Component) -SyncTrigger = time_ns.class_('SyncTrigger', automation.Trigger.template(), cg.Component) -ESPTime = time_ns.struct('ESPTime') -TimeHasTimeCondition = time_ns.class_('TimeHasTimeCondition', Condition) +time_ns = cg.esphome_ns.namespace("time") +RealTimeClock = time_ns.class_("RealTimeClock", cg.PollingComponent) +CronTrigger = time_ns.class_("CronTrigger", automation.Trigger.template(), cg.Component) +SyncTrigger = time_ns.class_("SyncTrigger", automation.Trigger.template(), cg.Component) +ESPTime = time_ns.struct("ESPTime") +TimeHasTimeCondition = time_ns.class_("TimeHasTimeCondition", Condition) def _tz_timedelta(td): @@ -34,12 +49,12 @@ def _tz_timedelta(td): offset_minute = int(abs(td.total_seconds() / 60)) % 60 offset_second = int(abs(td.total_seconds())) % 60 if offset_hour == 0 and offset_minute == 0 and offset_second == 0: - return '0' + return "0" if offset_minute == 0 and offset_second == 0: - return f'{offset_hour}' + return f"{offset_hour}" if offset_second == 0: - return f'{offset_hour}:{offset_minute}' - return f'{offset_hour}:{offset_minute}:{offset_second}' + return f"{offset_hour}:{offset_minute}" + return f"{offset_hour}:{offset_minute}:{offset_second}" # https://stackoverflow.com/a/16804556/8924614 @@ -52,8 +67,9 @@ def _week_of_month(dt): def _tz_dst_str(dt): td = datetime.timedelta(hours=dt.hour, minutes=dt.minute, seconds=dt.second) - return 'M{}.{}.{}/{}'.format(dt.month, _week_of_month(dt), dt.isoweekday() % 7, - _tz_timedelta(td)) + return "M{}.{}.{}/{}".format( + dt.month, _week_of_month(dt), dt.isoweekday() % 7, _tz_timedelta(td) + ) def _safe_tzname(tz, dt): @@ -62,16 +78,17 @@ def _safe_tzname(tz, dt): # For example: 'Europe/Saratov' returns '+04' # Work around it by using a generic name for the timezone if not all(c in string.ascii_letters for c in tzname): - return 'TZ' + return "TZ" return tzname def _non_dst_tz(tz, dt): tzname = _safe_tzname(tz, dt) utcoffset = tz.utcoffset(dt) - _LOGGER.info("Detected timezone '%s' with UTC offset %s", - tzname, _tz_timedelta(utcoffset)) - tzbase = '{}{}'.format(tzname, _tz_timedelta(-1 * utcoffset)) + _LOGGER.info( + "Detected timezone '%s' with UTC offset %s", tzname, _tz_timedelta(utcoffset) + ) + tzbase = "{}{}".format(tzname, _tz_timedelta(-1 * utcoffset)) return tzbase @@ -112,15 +129,22 @@ def convert_tz(pytz_obj): dst_ends_utc = transition_times[idx2] dst_ends_local = dst_ends_utc + utcoffset_on - tzbase = '{}{}'.format(tzname_off, _tz_timedelta(-1 * utcoffset_off)) - - tzext = '{}{},{},{}'.format(tzname_on, _tz_timedelta(-1 * utcoffset_on), - _tz_dst_str(dst_begins_local), _tz_dst_str(dst_ends_local)) - _LOGGER.info("Detected timezone '%s' with UTC offset %s and daylight savings time from " - "%s to %s", - tzname_off, _tz_timedelta(utcoffset_off), - dst_begins_local.strftime("%d %B %X"), - dst_ends_local.strftime("%d %B %X")) + tzbase = "{}{}".format(tzname_off, _tz_timedelta(-1 * utcoffset_off)) + + tzext = "{}{},{},{}".format( + tzname_on, + _tz_timedelta(-1 * utcoffset_on), + _tz_dst_str(dst_begins_local), + _tz_dst_str(dst_ends_local), + ) + _LOGGER.info( + "Detected timezone '%s' with UTC offset %s and daylight savings time from " + "%s to %s", + tzname_off, + _tz_timedelta(utcoffset_off), + dst_begins_local.strftime("%d %B %X"), + dst_ends_local.strftime("%d %B %X"), + ) return tzbase + tzext @@ -129,7 +153,7 @@ def detect_tz(): tz = tzlocal.get_localzone() except pytz.exceptions.UnknownTimeZoneError: _LOGGER.warning("Could not auto-detect timezone. Using UTC...") - return 'UTC' + return "UTC" return convert_tz(tz) @@ -146,42 +170,61 @@ def _parse_cron_int(value, special_mapping, message): def _parse_cron_part(part, min_value, max_value, special_mapping): - if part in ('*', '?'): + if part in ("*", "?"): return set(range(min_value, max_value + 1)) - if '/' in part: - data = part.split('/') + if "/" in part: + data = part.split("/") if len(data) > 2: - raise cv.Invalid("Can't have more than two '/' in one time expression, got {}" - .format(part)) + raise cv.Invalid( + "Can't have more than two '/' in one time expression, got {}".format( + part + ) + ) offset, repeat = data offset_n = 0 if offset: - offset_n = _parse_cron_int(offset, special_mapping, - "Offset for '/' time expression must be an integer, got {}") + offset_n = _parse_cron_int( + offset, + special_mapping, + "Offset for '/' time expression must be an integer, got {}", + ) try: repeat_n = int(repeat) except ValueError: # pylint: disable=raise-missing-from - raise cv.Invalid("Repeat for '/' time expression must be an integer, got {}" - .format(repeat)) + raise cv.Invalid( + "Repeat for '/' time expression must be an integer, got {}".format( + repeat + ) + ) return set(range(offset_n, max_value + 1, repeat_n)) - if '-' in part: - data = part.split('-') + if "-" in part: + data = part.split("-") if len(data) > 2: - raise cv.Invalid("Can't have more than two '-' in range time expression '{}'" - .format(part)) + raise cv.Invalid( + "Can't have more than two '-' in range time expression '{}'".format( + part + ) + ) begin, end = data - begin_n = _parse_cron_int(begin, special_mapping, "Number for time range must be integer, " - "got {}") - end_n = _parse_cron_int(end, special_mapping, "Number for time range must be integer, " - "got {}") + begin_n = _parse_cron_int( + begin, special_mapping, "Number for time range must be integer, " "got {}" + ) + end_n = _parse_cron_int( + end, special_mapping, "Number for time range must be integer, " "got {}" + ) if end_n < begin_n: return set(range(end_n, max_value + 1)) | set(range(min_value, begin_n + 1)) return set(range(begin_n, end_n + 1)) - return {_parse_cron_int(part, special_mapping, "Number for time expression must be an " - "integer, got {}")} + return { + _parse_cron_int( + part, + special_mapping, + "Number for time expression must be an " "integer, got {}", + ) + } def cron_expression_validator(name, min_value, max_value, special_mapping=None): @@ -190,42 +233,71 @@ def validator(value): for v in value: if not isinstance(v, int): raise cv.Invalid( - "Expected integer for {} '{}', got {}".format(v, name, type(v))) + "Expected integer for {} '{}', got {}".format(v, name, type(v)) + ) if v < min_value or v > max_value: raise cv.Invalid( - "{} {} is out of range (min={} max={}).".format(name, v, min_value, - max_value)) + "{} {} is out of range (min={} max={}).".format( + name, v, min_value, max_value + ) + ) return list(sorted(value)) value = cv.string(value) values = set() - for part in value.split(','): + for part in value.split(","): values |= _parse_cron_part(part, min_value, max_value, special_mapping) return validator(list(values)) return validator -validate_cron_seconds = cron_expression_validator('seconds', 0, 60) -validate_cron_minutes = cron_expression_validator('minutes', 0, 59) -validate_cron_hours = cron_expression_validator('hours', 0, 23) -validate_cron_days_of_month = cron_expression_validator('days of month', 1, 31) -validate_cron_months = cron_expression_validator('months', 1, 12, { - 'JAN': 1, 'FEB': 2, 'MAR': 3, 'APR': 4, 'MAY': 5, 'JUN': 6, 'JUL': 7, 'AUG': 8, - 'SEP': 9, 'OCT': 10, 'NOV': 11, 'DEC': 12 -}) -validate_cron_days_of_week = cron_expression_validator('days of week', 1, 7, { - 'SUN': 1, 'MON': 2, 'TUE': 3, 'WED': 4, 'THU': 5, 'FRI': 6, 'SAT': 7 -}) -CRON_KEYS = [CONF_SECONDS, CONF_MINUTES, CONF_HOURS, CONF_DAYS_OF_MONTH, CONF_MONTHS, - CONF_DAYS_OF_WEEK] +validate_cron_seconds = cron_expression_validator("seconds", 0, 60) +validate_cron_minutes = cron_expression_validator("minutes", 0, 59) +validate_cron_hours = cron_expression_validator("hours", 0, 23) +validate_cron_days_of_month = cron_expression_validator("days of month", 1, 31) +validate_cron_months = cron_expression_validator( + "months", + 1, + 12, + { + "JAN": 1, + "FEB": 2, + "MAR": 3, + "APR": 4, + "MAY": 5, + "JUN": 6, + "JUL": 7, + "AUG": 8, + "SEP": 9, + "OCT": 10, + "NOV": 11, + "DEC": 12, + }, +) +validate_cron_days_of_week = cron_expression_validator( + "days of week", + 1, + 7, + {"SUN": 1, "MON": 2, "TUE": 3, "WED": 4, "THU": 5, "FRI": 6, "SAT": 7}, +) +CRON_KEYS = [ + CONF_SECONDS, + CONF_MINUTES, + CONF_HOURS, + CONF_DAYS_OF_MONTH, + CONF_MONTHS, + CONF_DAYS_OF_WEEK, +] def validate_cron_raw(value): value = cv.string(value) - value = value.split(' ') + value = value.split(" ") if len(value) != 6: - raise cv.Invalid("Cron expression must consist of exactly 6 space-separated parts, " - "not {}".format(len(value))) + raise cv.Invalid( + "Cron expression must consist of exactly 6 space-separated parts, " + "not {}".format(len(value)) + ) seconds, minutes, hours, days_of_month, months, days_of_week = value return { CONF_SECONDS: validate_cron_seconds(seconds), @@ -243,9 +315,9 @@ def validate_time_at(value): CONF_HOURS: [value[CONF_HOUR]], CONF_MINUTES: [value[CONF_MINUTE]], CONF_SECONDS: [value[CONF_SECOND]], - CONF_DAYS_OF_MONTH: validate_cron_days_of_month('*'), - CONF_MONTHS: validate_cron_months('*'), - CONF_DAYS_OF_WEEK: validate_cron_days_of_week('*'), + CONF_DAYS_OF_MONTH: validate_cron_days_of_month("*"), + CONF_MONTHS: validate_cron_months("*"), + CONF_DAYS_OF_WEEK: validate_cron_days_of_week("*"), } @@ -282,23 +354,30 @@ def validate_tz(value): return convert_tz(pytz_obj) -TIME_SCHEMA = cv.Schema({ - cv.Optional(CONF_TIMEZONE, default=detect_tz): validate_tz, - cv.Optional(CONF_ON_TIME): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CronTrigger), - cv.Optional(CONF_SECONDS): validate_cron_seconds, - cv.Optional(CONF_MINUTES): validate_cron_minutes, - cv.Optional(CONF_HOURS): validate_cron_hours, - cv.Optional(CONF_DAYS_OF_MONTH): validate_cron_days_of_month, - cv.Optional(CONF_MONTHS): validate_cron_months, - cv.Optional(CONF_DAYS_OF_WEEK): validate_cron_days_of_week, - cv.Optional(CONF_CRON): validate_cron_raw, - cv.Optional(CONF_AT): validate_time_at, - }, validate_cron_keys), - cv.Optional(CONF_ON_TIME_SYNC): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SyncTrigger), - }), -}).extend(cv.polling_component_schema('15min')) +TIME_SCHEMA = cv.Schema( + { + cv.Optional(CONF_TIMEZONE, default=detect_tz): validate_tz, + cv.Optional(CONF_ON_TIME): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CronTrigger), + cv.Optional(CONF_SECONDS): validate_cron_seconds, + cv.Optional(CONF_MINUTES): validate_cron_minutes, + cv.Optional(CONF_HOURS): validate_cron_hours, + cv.Optional(CONF_DAYS_OF_MONTH): validate_cron_days_of_month, + cv.Optional(CONF_MONTHS): validate_cron_months, + cv.Optional(CONF_DAYS_OF_WEEK): validate_cron_days_of_week, + cv.Optional(CONF_CRON): validate_cron_raw, + cv.Optional(CONF_AT): validate_time_at, + }, + validate_cron_keys, + ), + cv.Optional(CONF_ON_TIME_SYNC): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SyncTrigger), + } + ), + } +).extend(cv.polling_component_schema("15min")) @coroutine @@ -338,13 +417,19 @@ def register_time(time_var, config): @coroutine_with_priority(100.0) def to_code(config): - cg.add_define('USE_TIME') + cg.add_define("USE_TIME") cg.add_global(time_ns.using) -@automation.register_condition('time.has_time', TimeHasTimeCondition, cv.Schema({ - cv.GenerateID(): cv.use_id(RealTimeClock), -})) +@automation.register_condition( + "time.has_time", + TimeHasTimeCondition, + cv.Schema( + { + cv.GenerateID(): cv.use_id(RealTimeClock), + } + ), +) def time_has_time_to_code(config, condition_id, template_arg, args): paren = yield cg.get_variable(config[CONF_ID]) yield cg.new_Pvariable(condition_id, template_arg, paren) diff --git a/esphome/components/time_based/cover.py b/esphome/components/time_based/cover.py index dcb8d9505b1f..9246f788842d 100644 --- a/esphome/components/time_based/cover.py +++ b/esphome/components/time_based/cover.py @@ -2,27 +2,33 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import cover -from esphome.const import CONF_CLOSE_ACTION, CONF_CLOSE_DURATION, CONF_ID, CONF_OPEN_ACTION, \ - CONF_OPEN_DURATION, CONF_STOP_ACTION, CONF_ASSUMED_STATE - -time_based_ns = cg.esphome_ns.namespace('time_based') -TimeBasedCover = time_based_ns.class_('TimeBasedCover', cover.Cover, cg.Component) - -CONF_HAS_BUILT_IN_ENDSTOP = 'has_built_in_endstop' - -CONFIG_SCHEMA = cover.COVER_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TimeBasedCover), - cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True), - - cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True), - cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds, - - cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True), - cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds, - - cv.Optional(CONF_HAS_BUILT_IN_ENDSTOP, default=False): cv.boolean, - cv.Optional(CONF_ASSUMED_STATE, default=True): cv.boolean, -}).extend(cv.COMPONENT_SCHEMA) +from esphome.const import ( + CONF_CLOSE_ACTION, + CONF_CLOSE_DURATION, + CONF_ID, + CONF_OPEN_ACTION, + CONF_OPEN_DURATION, + CONF_STOP_ACTION, + CONF_ASSUMED_STATE, +) + +time_based_ns = cg.esphome_ns.namespace("time_based") +TimeBasedCover = time_based_ns.class_("TimeBasedCover", cover.Cover, cg.Component) + +CONF_HAS_BUILT_IN_ENDSTOP = "has_built_in_endstop" + +CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TimeBasedCover), + cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds, + cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True), + cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds, + cv.Optional(CONF_HAS_BUILT_IN_ENDSTOP, default=False): cv.boolean, + cv.Optional(CONF_ASSUMED_STATE, default=True): cv.boolean, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): @@ -30,13 +36,19 @@ def to_code(config): yield cg.register_component(var, config) yield cover.register_cover(var, config) - yield automation.build_automation(var.get_stop_trigger(), [], config[CONF_STOP_ACTION]) + yield automation.build_automation( + var.get_stop_trigger(), [], config[CONF_STOP_ACTION] + ) cg.add(var.set_open_duration(config[CONF_OPEN_DURATION])) - yield automation.build_automation(var.get_open_trigger(), [], config[CONF_OPEN_ACTION]) + yield automation.build_automation( + var.get_open_trigger(), [], config[CONF_OPEN_ACTION] + ) cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION])) - yield automation.build_automation(var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]) + yield automation.build_automation( + var.get_close_trigger(), [], config[CONF_CLOSE_ACTION] + ) cg.add(var.set_has_built_in_endstop(config[CONF_HAS_BUILT_IN_ENDSTOP])) cg.add(var.set_assumed_state(config[CONF_ASSUMED_STATE])) diff --git a/esphome/components/tlc59208f/__init__.py b/esphome/components/tlc59208f/__init__.py index 4666b63b46fa..ff5b75954b09 100644 --- a/esphome/components/tlc59208f/__init__.py +++ b/esphome/components/tlc59208f/__init__.py @@ -3,15 +3,21 @@ from esphome.components import i2c from esphome.const import CONF_ID -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] MULTI_CONF = True -tlc59208f_ns = cg.esphome_ns.namespace('tlc59208f') -TLC59208FOutput = tlc59208f_ns.class_('TLC59208FOutput', cg.Component, i2c.I2CDevice) +tlc59208f_ns = cg.esphome_ns.namespace("tlc59208f") +TLC59208FOutput = tlc59208f_ns.class_("TLC59208FOutput", cg.Component, i2c.I2CDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(TLC59208FOutput), -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x20)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TLC59208FOutput), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x20)) +) def to_code(config): diff --git a/esphome/components/tlc59208f/output.py b/esphome/components/tlc59208f/output.py index f61f7729e73e..94cf529a75f0 100644 --- a/esphome/components/tlc59208f/output.py +++ b/esphome/components/tlc59208f/output.py @@ -4,17 +4,18 @@ from esphome.const import CONF_CHANNEL, CONF_ID from . import TLC59208FOutput, tlc59208f_ns -DEPENDENCIES = ['tlc59208f'] +DEPENDENCIES = ["tlc59208f"] -TLC59208FChannel = tlc59208f_ns.class_('TLC59208FChannel', output.FloatOutput) -CONF_TLC59208F_ID = 'tlc59208f_id' +TLC59208FChannel = tlc59208f_ns.class_("TLC59208FChannel", output.FloatOutput) +CONF_TLC59208F_ID = "tlc59208f_id" -CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ - cv.Required(CONF_ID): cv.declare_id(TLC59208FChannel), - cv.GenerateID(CONF_TLC59208F_ID): cv.use_id(TLC59208FOutput), - - cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), -}) +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(TLC59208FChannel), + cv.GenerateID(CONF_TLC59208F_ID): cv.use_id(TLC59208FOutput), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), + } +) def to_code(config): diff --git a/esphome/components/tm1637/display.py b/esphome/components/tm1637/display.py index c2692e30dee9..06a9716e5942 100644 --- a/esphome/components/tm1637/display.py +++ b/esphome/components/tm1637/display.py @@ -2,21 +2,30 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display -from esphome.const import CONF_CLK_PIN, CONF_DIO_PIN, CONF_ID, CONF_LAMBDA, CONF_INTENSITY - -CODEOWNERS = ['@glmnet'] - -tm1637_ns = cg.esphome_ns.namespace('tm1637') -TM1637Display = tm1637_ns.class_('TM1637Display', cg.PollingComponent) -TM1637DisplayRef = TM1637Display.operator('ref') - -CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TM1637Display), - - cv.Optional(CONF_INTENSITY, default=7): cv.All(cv.uint8_t, cv.Range(min=0, max=7)), - cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_DIO_PIN): pins.gpio_output_pin_schema, -}).extend(cv.polling_component_schema('1s')) +from esphome.const import ( + CONF_CLK_PIN, + CONF_DIO_PIN, + CONF_ID, + CONF_LAMBDA, + CONF_INTENSITY, +) + +CODEOWNERS = ["@glmnet"] + +tm1637_ns = cg.esphome_ns.namespace("tm1637") +TM1637Display = tm1637_ns.class_("TM1637Display", cg.PollingComponent) +TM1637DisplayRef = TM1637Display.operator("ref") + +CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TM1637Display), + cv.Optional(CONF_INTENSITY, default=7): cv.All( + cv.uint8_t, cv.Range(min=0, max=7) + ), + cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_DIO_PIN): pins.gpio_output_pin_schema, + } +).extend(cv.polling_component_schema("1s")) def to_code(config): @@ -32,6 +41,7 @@ def to_code(config): cg.add(var.set_intensity(config[CONF_INTENSITY])) if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(TM1637DisplayRef, 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], [(TM1637DisplayRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/tm1651/__init__.py b/esphome/components/tm1651/__init__.py index aa972552f45f..97c1e472e9f2 100644 --- a/esphome/components/tm1651/__init__.py +++ b/esphome/components/tm1651/__init__.py @@ -2,30 +2,38 @@ import esphome.config_validation as cv from esphome import pins, automation from esphome.automation import maybe_simple_id -from esphome.const import CONF_ID, CONF_CLK_PIN, CONF_DIO_PIN, CONF_LEVEL, CONF_BRIGHTNESS +from esphome.const import ( + CONF_ID, + CONF_CLK_PIN, + CONF_DIO_PIN, + CONF_LEVEL, + CONF_BRIGHTNESS, +) -tm1651_ns = cg.esphome_ns.namespace('tm1651') -TM1651Display = tm1651_ns.class_('TM1651Display', cg.Component) +tm1651_ns = cg.esphome_ns.namespace("tm1651") +TM1651Display = tm1651_ns.class_("TM1651Display", cg.Component) -SetLevelPercentAction = tm1651_ns.class_('SetLevelPercentAction', automation.Action) -SetLevelAction = tm1651_ns.class_('SetLevelAction', automation.Action) -SetBrightnessAction = tm1651_ns.class_('SetBrightnessAction', automation.Action) -TurnOnAction = tm1651_ns.class_('SetLevelPercentAction', automation.Action) -TurnOffAction = tm1651_ns.class_('SetLevelPercentAction', automation.Action) +SetLevelPercentAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action) +SetLevelAction = tm1651_ns.class_("SetLevelAction", automation.Action) +SetBrightnessAction = tm1651_ns.class_("SetBrightnessAction", automation.Action) +TurnOnAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action) +TurnOffAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action) -CONF_LEVEL_PERCENT = 'level_percent' +CONF_LEVEL_PERCENT = "level_percent" TM1651_BRIGHTNESS_OPTIONS = { 1: TM1651Display.TM1651_BRIGHTNESS_LOW, 2: TM1651Display.TM1651_BRIGHTNESS_MEDIUM, - 3: TM1651Display.TM1651_BRIGHTNESS_HIGH + 3: TM1651Display.TM1651_BRIGHTNESS_HIGH, } -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(TM1651Display), - cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_schema, - cv.Required(CONF_DIO_PIN): pins.internal_gpio_output_pin_schema, -}) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(TM1651Display), + cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_DIO_PIN): pins.internal_gpio_output_pin_schema, + } +) validate_level_percent = cv.All(cv.int_range(min=0, max=100)) validate_level = cv.All(cv.int_range(min=0, max=7)) @@ -42,22 +50,26 @@ def to_code(config): cg.add(var.set_dio_pin(dio_pin)) # https://platformio.org/lib/show/6865/TM1651 - cg.add_library('6865', '1.0.1') + cg.add_library("6865", "1.0.1") -BINARY_OUTPUT_ACTION_SCHEMA = maybe_simple_id({ - cv.Required(CONF_ID): cv.use_id(TM1651Display), -}) +BINARY_OUTPUT_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(TM1651Display), + } +) -@automation.register_action('tm1651.turn_on', TurnOnAction, BINARY_OUTPUT_ACTION_SCHEMA) +@automation.register_action("tm1651.turn_on", TurnOnAction, BINARY_OUTPUT_ACTION_SCHEMA) def output_turn_on_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) yield var -@automation.register_action('tm1651.turn_off', TurnOffAction, BINARY_OUTPUT_ACTION_SCHEMA) +@automation.register_action( + "tm1651.turn_off", TurnOffAction, BINARY_OUTPUT_ACTION_SCHEMA +) def output_turn_off_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) @@ -65,12 +77,16 @@ def output_turn_off_to_code(config, action_id, template_arg, args): @automation.register_action( - 'tm1651.set_level_percent', + "tm1651.set_level_percent", SetLevelPercentAction, - cv.maybe_simple_value({ - cv.GenerateID(): cv.use_id(TM1651Display), - cv.Required(CONF_LEVEL_PERCENT): cv.templatable(validate_level_percent), - }, key=CONF_LEVEL_PERCENT)) + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(TM1651Display), + cv.Required(CONF_LEVEL_PERCENT): cv.templatable(validate_level_percent), + }, + key=CONF_LEVEL_PERCENT, + ), +) def tm1651_set_level_percent_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) @@ -80,12 +96,16 @@ def tm1651_set_level_percent_to_code(config, action_id, template_arg, args): @automation.register_action( - 'tm1651.set_level', + "tm1651.set_level", SetLevelAction, - cv.maybe_simple_value({ - cv.GenerateID(): cv.use_id(TM1651Display), - cv.Required(CONF_LEVEL): cv.templatable(validate_level), - }, key=CONF_LEVEL)) + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(TM1651Display), + cv.Required(CONF_LEVEL): cv.templatable(validate_level), + }, + key=CONF_LEVEL, + ), +) def tm1651_set_level_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) @@ -95,12 +115,16 @@ def tm1651_set_level_to_code(config, action_id, template_arg, args): @automation.register_action( - 'tm1651.set_brightness', + "tm1651.set_brightness", SetBrightnessAction, - cv.maybe_simple_value({ - cv.GenerateID(): cv.use_id(TM1651Display), - cv.Required(CONF_BRIGHTNESS): cv.templatable(validate_brightness), - }, key=CONF_BRIGHTNESS)) + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(TM1651Display), + cv.Required(CONF_BRIGHTNESS): cv.templatable(validate_brightness), + }, + key=CONF_BRIGHTNESS, + ), +) def tm1651_set_brightness_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/tmp102/sensor.py b/esphome/components/tmp102/sensor.py index d60f0a86466e..4c04271d96c6 100644 --- a/esphome/components/tmp102/sensor.py +++ b/esphome/components/tmp102/sensor.py @@ -10,18 +10,26 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, UNIT_CELSIUS, ICON_THERMOMETER +from esphome.const import CONF_ID, DEVICE_CLASS_TEMPERATURE, ICON_EMPTY, UNIT_CELSIUS -CODEOWNERS = ['@timsavage'] -DEPENDENCIES = ['i2c'] +CODEOWNERS = ["@timsavage"] +DEPENDENCIES = ["i2c"] -tmp102_ns = cg.esphome_ns.namespace('tmp102') -TMP102Component = tmp102_ns.class_("TMP102Component", cg.PollingComponent, i2c.I2CDevice, - sensor.Sensor) +tmp102_ns = cg.esphome_ns.namespace("tmp102") +TMP102Component = tmp102_ns.class_( + "TMP102Component", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.GenerateID(): cv.declare_id(TMP102Component), -}).extend(cv.polling_component_schema("60s")).extend(i2c.i2c_device_schema(0x48)) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE) + .extend( + { + cv.GenerateID(): cv.declare_id(TMP102Component), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x48)) +) def to_code(config): diff --git a/esphome/components/tmp117/sensor.py b/esphome/components/tmp117/sensor.py index ddca3eeb6441..33c13e3f3bae 100644 --- a/esphome/components/tmp117/sensor.py +++ b/esphome/components/tmp117/sensor.py @@ -1,18 +1,31 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, CONF_UPDATE_INTERVAL, \ - UNIT_CELSIUS, ICON_THERMOMETER +from esphome.const import ( + CONF_ID, + CONF_UPDATE_INTERVAL, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -tmp117_ns = cg.esphome_ns.namespace('tmp117') -TMP117Component = tmp117_ns.class_('TMP117Component', - cg.PollingComponent, i2c.I2CDevice, sensor.Sensor) +tmp117_ns = cg.esphome_ns.namespace("tmp117") +TMP117Component = tmp117_ns.class_( + "TMP117Component", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor +) -CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ - cv.GenerateID(): cv.declare_id(TMP117Component), -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x48))) +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE) + .extend( + { + cv.GenerateID(): cv.declare_id(TMP117Component), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x48)) +) def determine_config_register(polling_period): diff --git a/esphome/components/toshiba/climate.py b/esphome/components/toshiba/climate.py index ea7efbf2f5ad..3021697f2137 100644 --- a/esphome/components/toshiba/climate.py +++ b/esphome/components/toshiba/climate.py @@ -3,14 +3,16 @@ from esphome.components import climate_ir from esphome.const import CONF_ID -AUTO_LOAD = ['climate_ir'] +AUTO_LOAD = ["climate_ir"] -toshiba_ns = cg.esphome_ns.namespace('toshiba') -ToshibaClimate = toshiba_ns.class_('ToshibaClimate', climate_ir.ClimateIR) +toshiba_ns = cg.esphome_ns.namespace("toshiba") +ToshibaClimate = toshiba_ns.class_("ToshibaClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(ToshibaClimate), -}) +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ToshibaClimate), + } +) def to_code(config): diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index 7fde9f5582fa..150cab77b42f 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -3,17 +3,21 @@ from esphome.components import sensor, time from esphome.const import CONF_ID, CONF_TIME_ID -DEPENDENCIES = ['time'] +DEPENDENCIES = ["time"] -CONF_POWER_ID = 'power_id' -total_daily_energy_ns = cg.esphome_ns.namespace('total_daily_energy') -TotalDailyEnergy = total_daily_energy_ns.class_('TotalDailyEnergy', sensor.Sensor, cg.Component) +CONF_POWER_ID = "power_id" +total_daily_energy_ns = cg.esphome_ns.namespace("total_daily_energy") +TotalDailyEnergy = total_daily_energy_ns.class_( + "TotalDailyEnergy", sensor.Sensor, cg.Component +) -CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TotalDailyEnergy), - cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), - cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TotalDailyEnergy), + cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/tsl2561/sensor.py b/esphome/components/tsl2561/sensor.py index d51ece09d52f..d0219fc07855 100644 --- a/esphome/components/tsl2561/sensor.py +++ b/esphome/components/tsl2561/sensor.py @@ -1,25 +1,32 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_GAIN, CONF_ID, CONF_INTEGRATION_TIME, UNIT_LUX, ICON_BRIGHTNESS_5 +from esphome.const import ( + CONF_GAIN, + CONF_ID, + CONF_INTEGRATION_TIME, + DEVICE_CLASS_ILLUMINANCE, + ICON_EMPTY, + UNIT_LUX, +) -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -tsl2561_ns = cg.esphome_ns.namespace('tsl2561') -TSL2561IntegrationTime = tsl2561_ns.enum('TSL2561IntegrationTime') +tsl2561_ns = cg.esphome_ns.namespace("tsl2561") +TSL2561IntegrationTime = tsl2561_ns.enum("TSL2561IntegrationTime") INTEGRATION_TIMES = { 14: TSL2561IntegrationTime.TSL2561_INTEGRATION_14MS, 101: TSL2561IntegrationTime.TSL2561_INTEGRATION_101MS, 402: TSL2561IntegrationTime.TSL2561_INTEGRATION_402MS, } -TSL2561Gain = tsl2561_ns.enum('TSL2561Gain') +TSL2561Gain = tsl2561_ns.enum("TSL2561Gain") GAINS = { - '1X': TSL2561Gain.TSL2561_GAIN_1X, - '16X': TSL2561Gain.TSL2561_GAIN_16X, + "1X": TSL2561Gain.TSL2561_GAIN_1X, + "16X": TSL2561Gain.TSL2561_GAIN_16X, } -CONF_IS_CS_PACKAGE = 'is_cs_package' +CONF_IS_CS_PACKAGE = "is_cs_package" def validate_integration_time(value): @@ -27,15 +34,25 @@ def validate_integration_time(value): return cv.enum(INTEGRATION_TIMES, int=True)(value) -TSL2561Sensor = tsl2561_ns.class_('TSL2561Sensor', sensor.Sensor, cg.PollingComponent, - i2c.I2CDevice) +TSL2561Sensor = tsl2561_ns.class_( + "TSL2561Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({ - cv.GenerateID(): cv.declare_id(TSL2561Sensor), - cv.Optional(CONF_INTEGRATION_TIME, default='402ms'): validate_integration_time, - cv.Optional(CONF_GAIN, default='1X'): cv.enum(GAINS, upper=True), - cv.Optional(CONF_IS_CS_PACKAGE, default=False): cv.boolean, -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39)) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE) + .extend( + { + cv.GenerateID(): cv.declare_id(TSL2561Sensor), + cv.Optional( + CONF_INTEGRATION_TIME, default="402ms" + ): validate_integration_time, + cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAINS, upper=True), + cv.Optional(CONF_IS_CS_PACKAGE, default=False): cv.boolean, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x39)) +) def to_code(config): diff --git a/esphome/components/ttp229_bsf/__init__.py b/esphome/components/ttp229_bsf/__init__.py index 5ec182d46b62..8707c9efe3e7 100644 --- a/esphome/components/ttp229_bsf/__init__.py +++ b/esphome/components/ttp229_bsf/__init__.py @@ -3,20 +3,22 @@ from esphome import pins from esphome.const import CONF_ID, CONF_SDO_PIN, CONF_SCL_PIN -DEPENDENCIES = ['i2c'] -AUTO_LOAD = ['binary_sensor'] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["binary_sensor"] -CONF_TTP229_ID = 'ttp229_id' -ttp229_bsf_ns = cg.esphome_ns.namespace('ttp229_bsf') +CONF_TTP229_ID = "ttp229_id" +ttp229_bsf_ns = cg.esphome_ns.namespace("ttp229_bsf") -TTP229BSFComponent = ttp229_bsf_ns.class_('TTP229BSFComponent', cg.Component) +TTP229BSFComponent = ttp229_bsf_ns.class_("TTP229BSFComponent", cg.Component) MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(TTP229BSFComponent), - cv.Required(CONF_SDO_PIN): pins.gpio_input_pullup_pin_schema, - cv.Required(CONF_SCL_PIN): pins.gpio_output_pin_schema, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(TTP229BSFComponent), + cv.Required(CONF_SDO_PIN): pins.gpio_input_pullup_pin_schema, + cv.Required(CONF_SCL_PIN): pins.gpio_output_pin_schema, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/ttp229_bsf/binary_sensor.py b/esphome/components/ttp229_bsf/binary_sensor.py index 7a1ab3dc9f04..7351e73d8086 100644 --- a/esphome/components/ttp229_bsf/binary_sensor.py +++ b/esphome/components/ttp229_bsf/binary_sensor.py @@ -4,14 +4,16 @@ from esphome.const import CONF_CHANNEL, CONF_ID from . import ttp229_bsf_ns, TTP229BSFComponent, CONF_TTP229_ID -DEPENDENCIES = ['ttp229_bsf'] -TTP229BSFChannel = ttp229_bsf_ns.class_('TTP229BSFChannel', binary_sensor.BinarySensor) +DEPENDENCIES = ["ttp229_bsf"] +TTP229BSFChannel = ttp229_bsf_ns.class_("TTP229BSFChannel", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TTP229BSFChannel), - cv.GenerateID(CONF_TTP229_ID): cv.use_id(TTP229BSFComponent), - cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=15), -}) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TTP229BSFChannel), + cv.GenerateID(CONF_TTP229_ID): cv.use_id(TTP229BSFComponent), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=15), + } +) def to_code(config): diff --git a/esphome/components/ttp229_lsf/__init__.py b/esphome/components/ttp229_lsf/__init__.py index 6faca970f09c..1d7efea205e2 100644 --- a/esphome/components/ttp229_lsf/__init__.py +++ b/esphome/components/ttp229_lsf/__init__.py @@ -3,18 +3,26 @@ from esphome.components import i2c from esphome.const import CONF_ID -DEPENDENCIES = ['i2c'] -AUTO_LOAD = ['binary_sensor'] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["binary_sensor"] -CONF_TTP229_ID = 'ttp229_id' -ttp229_lsf_ns = cg.esphome_ns.namespace('ttp229_lsf') +CONF_TTP229_ID = "ttp229_id" +ttp229_lsf_ns = cg.esphome_ns.namespace("ttp229_lsf") -TTP229LSFComponent = ttp229_lsf_ns.class_('TTP229LSFComponent', cg.Component, i2c.I2CDevice) +TTP229LSFComponent = ttp229_lsf_ns.class_( + "TTP229LSFComponent", cg.Component, i2c.I2CDevice +) MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(TTP229LSFComponent), -}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x57)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TTP229LSFComponent), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x57)) +) def to_code(config): diff --git a/esphome/components/ttp229_lsf/binary_sensor.py b/esphome/components/ttp229_lsf/binary_sensor.py index 870bf1628732..09a8a1e207c6 100644 --- a/esphome/components/ttp229_lsf/binary_sensor.py +++ b/esphome/components/ttp229_lsf/binary_sensor.py @@ -4,14 +4,16 @@ from esphome.const import CONF_CHANNEL, CONF_ID from . import ttp229_lsf_ns, TTP229LSFComponent, CONF_TTP229_ID -DEPENDENCIES = ['ttp229_lsf'] -TTP229Channel = ttp229_lsf_ns.class_('TTP229Channel', binary_sensor.BinarySensor) +DEPENDENCIES = ["ttp229_lsf"] +TTP229Channel = ttp229_lsf_ns.class_("TTP229Channel", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TTP229Channel), - cv.GenerateID(CONF_TTP229_ID): cv.use_id(TTP229LSFComponent), - cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=15), -}) +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TTP229Channel), + cv.GenerateID(CONF_TTP229_ID): cv.use_id(TTP229LSFComponent), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=15), + } +) def to_code(config): diff --git a/esphome/components/tuya/__init__.py b/esphome/components/tuya/__init__.py index 83a4f733ca11..58dad1325701 100644 --- a/esphome/components/tuya/__init__.py +++ b/esphome/components/tuya/__init__.py @@ -4,19 +4,27 @@ from esphome.components import uart from esphome.const import CONF_ID, CONF_TIME_ID -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints" -tuya_ns = cg.esphome_ns.namespace('tuya') -Tuya = tuya_ns.class_('Tuya', cg.Component, uart.UARTDevice) +tuya_ns = cg.esphome_ns.namespace("tuya") +Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice) -CONF_TUYA_ID = 'tuya_id' -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(Tuya), - cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), - cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list(cv.uint8_t), -}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA) +CONF_TUYA_ID = "tuya_id" +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Tuya), + cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list( + cv.uint8_t + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) def to_code(config): diff --git a/esphome/components/tuya/binary_sensor/__init__.py b/esphome/components/tuya/binary_sensor/__init__.py index b63638b4cc0f..45f918ff24f3 100644 --- a/esphome/components/tuya/binary_sensor/__init__.py +++ b/esphome/components/tuya/binary_sensor/__init__.py @@ -4,18 +4,22 @@ from esphome.const import CONF_ID from .. import tuya_ns, CONF_TUYA_ID, Tuya -DEPENDENCIES = ['tuya'] -CODEOWNERS = ['@jesserockz'] +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@jesserockz"] CONF_SENSOR_DATAPOINT = "sensor_datapoint" -TuyaBinarySensor = tuya_ns.class_('TuyaBinarySensor', binary_sensor.BinarySensor, cg.Component) - -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TuyaBinarySensor), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, -}).extend(cv.COMPONENT_SCHEMA) +TuyaBinarySensor = tuya_ns.class_( + "TuyaBinarySensor", binary_sensor.BinarySensor, cg.Component +) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaBinarySensor), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index f0219de97bf2..8ac42f7c8f0e 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -4,60 +4,77 @@ from esphome.const import CONF_ID, CONF_SWITCH_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya -DEPENDENCIES = ['tuya'] -CODEOWNERS = ['@jesserockz'] +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@jesserockz"] -CONF_TARGET_TEMPERATURE_DATAPOINT = 'target_temperature_datapoint' -CONF_CURRENT_TEMPERATURE_DATAPOINT = 'current_temperature_datapoint' -CONF_TEMPERATURE_MULTIPLIER = 'temperature_multiplier' -CONF_CURRENT_TEMPERATURE_MULTIPLIER = 'current_temperature_multiplier' -CONF_TARGET_TEMPERATURE_MULTIPLIER = 'target_temperature_multiplier' +CONF_TARGET_TEMPERATURE_DATAPOINT = "target_temperature_datapoint" +CONF_CURRENT_TEMPERATURE_DATAPOINT = "current_temperature_datapoint" +CONF_TEMPERATURE_MULTIPLIER = "temperature_multiplier" +CONF_CURRENT_TEMPERATURE_MULTIPLIER = "current_temperature_multiplier" +CONF_TARGET_TEMPERATURE_MULTIPLIER = "target_temperature_multiplier" -TuyaClimate = tuya_ns.class_('TuyaClimate', climate.Climate, cg.Component) +TuyaClimate = tuya_ns.class_("TuyaClimate", climate.Climate, cg.Component) def validate_temperature_multipliers(value): if CONF_TEMPERATURE_MULTIPLIER in value: if ( - CONF_CURRENT_TEMPERATURE_MULTIPLIER in value - or CONF_TARGET_TEMPERATURE_MULTIPLIER in value + CONF_CURRENT_TEMPERATURE_MULTIPLIER in value + or CONF_TARGET_TEMPERATURE_MULTIPLIER in value ): - raise cv.Invalid((f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as " - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and " - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}")) + raise cv.Invalid( + ( + f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as " + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and " + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" + ) + ) if ( - CONF_CURRENT_TEMPERATURE_MULTIPLIER in value - and CONF_TARGET_TEMPERATURE_MULTIPLIER not in value + CONF_CURRENT_TEMPERATURE_MULTIPLIER in value + and CONF_TARGET_TEMPERATURE_MULTIPLIER not in value ): - raise cv.Invalid((f"{CONF_TARGET_TEMPERATURE_MULTIPLIER} required if using " - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER}")) + raise cv.Invalid( + ( + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER} required if using " + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER}" + ) + ) if ( - CONF_TARGET_TEMPERATURE_MULTIPLIER in value - and CONF_CURRENT_TEMPERATURE_MULTIPLIER not in value + CONF_TARGET_TEMPERATURE_MULTIPLIER in value + and CONF_CURRENT_TEMPERATURE_MULTIPLIER not in value ): - raise cv.Invalid((f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} required if using " - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}")) + raise cv.Invalid( + ( + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} required if using " + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" + ) + ) keys = ( CONF_TEMPERATURE_MULTIPLIER, CONF_CURRENT_TEMPERATURE_MULTIPLIER, - CONF_TARGET_TEMPERATURE_MULTIPLIER + CONF_TARGET_TEMPERATURE_MULTIPLIER, ) if all(multiplier not in value for multiplier in keys): value[CONF_TEMPERATURE_MULTIPLIER] = 1.0 return value -CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TuyaClimate), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_TARGET_TEMPERATURE_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_CURRENT_TEMPERATURE_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float, - cv.Optional(CONF_CURRENT_TEMPERATURE_MULTIPLIER): cv.positive_float, - cv.Optional(CONF_TARGET_TEMPERATURE_MULTIPLIER): cv.positive_float, -}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key( - CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), validate_temperature_multipliers) +CONFIG_SCHEMA = cv.All( + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaClimate), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_TARGET_TEMPERATURE_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_CURRENT_TEMPERATURE_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float, + cv.Optional(CONF_CURRENT_TEMPERATURE_MULTIPLIER): cv.positive_float, + cv.Optional(CONF_TARGET_TEMPERATURE_MULTIPLIER): cv.positive_float, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), + validate_temperature_multipliers, +) def to_code(config): @@ -73,10 +90,24 @@ def to_code(config): if CONF_TARGET_TEMPERATURE_DATAPOINT in config: cg.add(var.set_target_temperature_id(config[CONF_TARGET_TEMPERATURE_DATAPOINT])) if CONF_CURRENT_TEMPERATURE_DATAPOINT in config: - cg.add(var.set_current_temperature_id(config[CONF_CURRENT_TEMPERATURE_DATAPOINT])) + cg.add( + var.set_current_temperature_id(config[CONF_CURRENT_TEMPERATURE_DATAPOINT]) + ) if CONF_TEMPERATURE_MULTIPLIER in config: - cg.add(var.set_target_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER])) - cg.add(var.set_current_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER])) + cg.add( + var.set_target_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER]) + ) + cg.add( + var.set_current_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER]) + ) else: - cg.add(var.set_current_temperature_multiplier(config[CONF_CURRENT_TEMPERATURE_MULTIPLIER])) - cg.add(var.set_target_temperature_multiplier(config[CONF_TARGET_TEMPERATURE_MULTIPLIER])) + cg.add( + var.set_current_temperature_multiplier( + config[CONF_CURRENT_TEMPERATURE_MULTIPLIER] + ) + ) + cg.add( + var.set_target_temperature_multiplier( + config[CONF_TARGET_TEMPERATURE_MULTIPLIER] + ) + ) diff --git a/esphome/components/tuya/fan/__init__.py b/esphome/components/tuya/fan/__init__.py index e8492fd71b3a..8615f3ae850b 100644 --- a/esphome/components/tuya/fan/__init__.py +++ b/esphome/components/tuya/fan/__init__.py @@ -4,21 +4,25 @@ from esphome.const import CONF_OUTPUT_ID, CONF_SWITCH_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya -DEPENDENCIES = ['tuya'] +DEPENDENCIES = ["tuya"] CONF_SPEED_DATAPOINT = "speed_datapoint" CONF_OSCILLATION_DATAPOINT = "oscillation_datapoint" -TuyaFan = tuya_ns.class_('TuyaFan', cg.Component) - -CONFIG_SCHEMA = cv.All(fan.FAN_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaFan), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Optional(CONF_OSCILLATION_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_SPEED_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, -}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key( - CONF_SPEED_DATAPOINT, CONF_SWITCH_DATAPOINT)) +TuyaFan = tuya_ns.class_("TuyaFan", cg.Component) + +CONFIG_SCHEMA = cv.All( + fan.FAN_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaFan), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Optional(CONF_OSCILLATION_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_SPEED_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.has_at_least_one_key(CONF_SPEED_DATAPOINT, CONF_SWITCH_DATAPOINT), +) def to_code(config): diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 9850fa65ed20..718f292f5b50 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -1,4 +1,5 @@ #include "esphome/core/log.h" +#include "esphome/components/fan/fan_helpers.h" #include "tuya_fan.h" namespace esphome { @@ -7,18 +8,18 @@ namespace tuya { static const char *TAG = "tuya.fan"; void TuyaFan::setup() { - auto traits = fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), false); + auto traits = fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), false, 3); this->fan_->set_traits(traits); if (this->speed_id_.has_value()) { this->parent_->register_listener(*this->speed_id_, [this](TuyaDatapoint datapoint) { auto call = this->fan_->make_call(); if (datapoint.value_enum == 0x0) - call.set_speed(fan::FAN_SPEED_LOW); + call.set_speed(1); else if (datapoint.value_enum == 0x1) - call.set_speed(fan::FAN_SPEED_MEDIUM); + call.set_speed(2); else if (datapoint.value_enum == 0x2) - call.set_speed(fan::FAN_SPEED_HIGH); + call.set_speed(3); else ESP_LOGCONFIG(TAG, "Speed has invalid value %d", datapoint.value_enum); ESP_LOGD(TAG, "MCU reported speed of: %d", datapoint.value_enum); @@ -75,12 +76,7 @@ void TuyaFan::write_state() { TuyaDatapoint datapoint{}; datapoint.id = *this->speed_id_; datapoint.type = TuyaDatapointType::ENUM; - if (this->fan_->speed == fan::FAN_SPEED_LOW) - datapoint.value_enum = 0; - if (this->fan_->speed == fan::FAN_SPEED_MEDIUM) - datapoint.value_enum = 1; - if (this->fan_->speed == fan::FAN_SPEED_HIGH) - datapoint.value_enum = 2; + datapoint.value_enum = this->fan_->speed - 1; ESP_LOGD(TAG, "Setting speed: %d", datapoint.value_enum); this->parent_->set_datapoint_value(datapoint); } diff --git a/esphome/components/tuya/light/__init__.py b/esphome/components/tuya/light/__init__.py index 05605822cb97..f8026e47e8cc 100644 --- a/esphome/components/tuya/light/__init__.py +++ b/esphome/components/tuya/light/__init__.py @@ -1,32 +1,43 @@ from esphome.components import light import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_OUTPUT_ID, CONF_MIN_VALUE, CONF_MAX_VALUE, CONF_GAMMA_CORRECT, \ - CONF_DEFAULT_TRANSITION_LENGTH, CONF_SWITCH_DATAPOINT +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_MIN_VALUE, + CONF_MAX_VALUE, + CONF_GAMMA_CORRECT, + CONF_DEFAULT_TRANSITION_LENGTH, + CONF_SWITCH_DATAPOINT, +) from .. import tuya_ns, CONF_TUYA_ID, Tuya -DEPENDENCIES = ['tuya'] +DEPENDENCIES = ["tuya"] CONF_DIMMER_DATAPOINT = "dimmer_datapoint" CONF_MIN_VALUE_DATAPOINT = "min_value_datapoint" -TuyaLight = tuya_ns.class_('TuyaLight', light.LightOutput, cg.Component) - -CONFIG_SCHEMA = cv.All(light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend({ - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaLight), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_MIN_VALUE): cv.int_, - cv.Optional(CONF_MAX_VALUE): cv.int_, - - # Change the default gamma_correct and default transition length settings. - # The Tuya MCU handles transitions and gamma correction on its own. - cv.Optional(CONF_GAMMA_CORRECT, default=1.0): cv.positive_float, - cv.Optional(CONF_DEFAULT_TRANSITION_LENGTH, default='0s'): cv.positive_time_period_milliseconds, -}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_DIMMER_DATAPOINT, - CONF_SWITCH_DATAPOINT)) +TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component) + +CONFIG_SCHEMA = cv.All( + light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaLight), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_MIN_VALUE): cv.int_, + cv.Optional(CONF_MAX_VALUE): cv.int_, + # Change the default gamma_correct and default transition length settings. + # The Tuya MCU handles transitions and gamma correction on its own. + cv.Optional(CONF_GAMMA_CORRECT, default=1.0): cv.positive_float, + cv.Optional( + CONF_DEFAULT_TRANSITION_LENGTH, default="0s" + ): cv.positive_time_period_milliseconds, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.has_at_least_one_key(CONF_DIMMER_DATAPOINT, CONF_SWITCH_DATAPOINT), +) def to_code(config): diff --git a/esphome/components/tuya/sensor/__init__.py b/esphome/components/tuya/sensor/__init__.py index b3260bfe0bd0..0a02fb77a18e 100644 --- a/esphome/components/tuya/sensor/__init__.py +++ b/esphome/components/tuya/sensor/__init__.py @@ -4,18 +4,20 @@ from esphome.const import CONF_ID from .. import tuya_ns, CONF_TUYA_ID, Tuya -DEPENDENCIES = ['tuya'] -CODEOWNERS = ['@jesserockz'] +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@jesserockz"] CONF_SENSOR_DATAPOINT = "sensor_datapoint" -TuyaSensor = tuya_ns.class_('TuyaSensor', sensor.Sensor, cg.Component) +TuyaSensor = tuya_ns.class_("TuyaSensor", sensor.Sensor, cg.Component) -CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TuyaSensor), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaSensor), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/tuya/switch/__init__.py b/esphome/components/tuya/switch/__init__.py index f68bbbcdb6c4..4c4ccbf81447 100644 --- a/esphome/components/tuya/switch/__init__.py +++ b/esphome/components/tuya/switch/__init__.py @@ -4,16 +4,18 @@ from esphome.const import CONF_ID, CONF_SWITCH_DATAPOINT from .. import tuya_ns, CONF_TUYA_ID, Tuya -DEPENDENCIES = ['tuya'] -CODEOWNERS = ['@jesserockz'] +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@jesserockz"] -TuyaSwitch = tuya_ns.class_('TuyaSwitch', switch.Switch, cg.Component) +TuyaSwitch = tuya_ns.class_("TuyaSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(TuyaSwitch), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Required(CONF_SWITCH_DATAPOINT): cv.uint8_t, -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaSwitch), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_SWITCH_DATAPOINT): cv.uint8_t, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 75514dde197c..f4a72e810940 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -9,7 +9,7 @@ static const char *TAG = "tuya"; static const int COMMAND_DELAY = 50; void Tuya::setup() { - this->set_interval("heartbeat", 1000, [this] { this->schedule_empty_command_(TuyaCommandType::HEARTBEAT); }); + this->set_interval("heartbeat", 1000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); } void Tuya::loop() { @@ -18,15 +18,7 @@ void Tuya::loop() { this->read_byte(&c); this->handle_char_(c); } -} - -void Tuya::schedule_empty_command_(TuyaCommandType command) { - uint32_t delay = millis() - this->last_command_timestamp_; - if (delay > COMMAND_DELAY) { - send_empty_command_(command); - } else { - this->set_timeout(COMMAND_DELAY - delay, [this, command] { this->send_empty_command_(command); }); - } + process_command_queue_(); } void Tuya::dump_config() { @@ -122,7 +114,6 @@ void Tuya::handle_char_(uint8_t c) { } void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) { - this->last_command_timestamp_ = millis(); switch ((TuyaCommandType) command) { case TuyaCommandType::HEARTBEAT: ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]); @@ -132,7 +123,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff } if (this->init_state_ == TuyaInitState::INIT_HEARTBEAT) { this->init_state_ = TuyaInitState::INIT_PRODUCT; - this->schedule_empty_command_(TuyaCommandType::PRODUCT_QUERY); + this->send_empty_command_(TuyaCommandType::PRODUCT_QUERY); } break; case TuyaCommandType::PRODUCT_QUERY: { @@ -151,7 +142,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff } if (this->init_state_ == TuyaInitState::INIT_PRODUCT) { this->init_state_ = TuyaInitState::INIT_CONF; - this->schedule_empty_command_(TuyaCommandType::CONF_QUERY); + this->send_empty_command_(TuyaCommandType::CONF_QUERY); } break; } @@ -164,16 +155,13 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff // If mcu returned status gpio, then we can ommit sending wifi state if (this->gpio_status_ != -1) { this->init_state_ = TuyaInitState::INIT_DATAPOINT; - this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY); + this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); } else { this->init_state_ = TuyaInitState::INIT_WIFI; - this->set_timeout(COMMAND_DELAY, [this] { - // If we were following the spec to the letter we would send - // state updates until connected to both WiFi and API/MQTT. - // Instead we just claim to be connected immediately and move on. - uint8_t c[] = {0x04}; - this->send_command_(TuyaCommandType::WIFI_STATE, c, 1); - }); + // If we were following the spec to the letter we would send + // state updates until connected to both WiFi and API/MQTT. + // Instead we just claim to be connected immediately and move on. + this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_STATE, .payload = std::vector{0x04}}); } } break; @@ -181,7 +169,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff case TuyaCommandType::WIFI_STATE: if (this->init_state_ == TuyaInitState::INIT_WIFI) { this->init_state_ = TuyaInitState::INIT_DATAPOINT; - this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY); + this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); } break; case TuyaCommandType::WIFI_RESET: @@ -202,8 +190,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff case TuyaCommandType::DATAPOINT_QUERY: break; case TuyaCommandType::WIFI_TEST: { - uint8_t c[] = {0x00, 0x00}; - this->send_command_(TuyaCommandType::WIFI_TEST, c, 2); + this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_TEST, .payload = std::vector{0x00, 0x00}}); break; } case TuyaCommandType::LOCAL_TIME_QUERY: { @@ -213,28 +200,26 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff auto now = time_id->now(); if (now.is_valid()) { - this->set_timeout(COMMAND_DELAY, [this, now] { - uint8_t year = now.year - 2000; - uint8_t month = now.month; - uint8_t day_of_month = now.day_of_month; - uint8_t hour = now.hour; - uint8_t minute = now.minute; - uint8_t second = now.second; - // Tuya days starts from Monday, esphome uses Sunday as day 1 - uint8_t day_of_week = now.day_of_week - 1; - if (day_of_week == 0) { - day_of_week = 7; - } - uint8_t c[] = {0x01, year, month, day_of_month, hour, minute, second, day_of_week}; - this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8); - }); + uint8_t year = now.year - 2000; + uint8_t month = now.month; + uint8_t day_of_month = now.day_of_month; + uint8_t hour = now.hour; + uint8_t minute = now.minute; + uint8_t second = now.second; + // Tuya days starts from Monday, esphome uses Sunday as day 1 + uint8_t day_of_week = now.day_of_week - 1; + if (day_of_week == 0) { + day_of_week = 7; + } + this->send_command_(TuyaCommand{ + .cmd = TuyaCommandType::LOCAL_TIME_QUERY, + .payload = std::vector{0x01, year, month, day_of_month, hour, minute, second, day_of_week}}); } else { ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not valid"); // By spec we need to notify MCU that the time was not obtained - this->set_timeout(COMMAND_DELAY, [this] { - uint8_t c[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8); - }); + this->send_command_( + TuyaCommand{.cmd = TuyaCommandType::LOCAL_TIME_QUERY, + .payload = std::vector{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}); } } else { ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not configured"); @@ -321,24 +306,44 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { listener.on_datapoint(datapoint); } -void Tuya::send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len) { - uint8_t len_hi = len >> 8; - uint8_t len_lo = len >> 0; +void Tuya::send_raw_command_(TuyaCommand command) { + uint8_t len_hi = (uint8_t)(command.payload.size() >> 8); + uint8_t len_lo = (uint8_t)(command.payload.size() & 0xFF); uint8_t version = 0; - ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version, // NOLINT - hexencode(buffer, len).c_str(), this->init_state_); + this->last_command_timestamp_ = millis(); + + ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command.cmd, version, // NOLINT + hexencode(command.payload).c_str(), this->init_state_); - this->write_array({0x55, 0xAA, version, (uint8_t) command, len_hi, len_lo}); - if (len != 0) - this->write_array(buffer, len); + this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo}); + if (!command.payload.empty()) + this->write_array(command.payload.data(), command.payload.size()); - uint8_t checksum = 0x55 + 0xAA + (uint8_t) command + len_hi + len_lo; - for (int i = 0; i < len; i++) - checksum += buffer[i]; + uint8_t checksum = 0x55 + 0xAA + (uint8_t) command.cmd + len_hi + len_lo; + for (auto &data : command.payload) + checksum += data; this->write_byte(checksum); } +void Tuya::process_command_queue_() { + uint32_t delay = millis() - this->last_command_timestamp_; + // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly + if (delay > COMMAND_DELAY && !command_queue_.empty()) { + this->send_raw_command_(command_queue_.front()); + this->command_queue_.erase(command_queue_.begin()); + } +} + +void Tuya::send_command_(TuyaCommand command) { + command_queue_.push_back(command); + process_command_queue_(); +} + +void Tuya::send_empty_command_(TuyaCommandType command) { + send_command_(TuyaCommand{.cmd = command, .payload = std::vector{0x04}}); +} + void Tuya::set_datapoint_value(TuyaDatapoint datapoint) { std::vector buffer; ESP_LOGV(TAG, "Datapoint %u set to %u", datapoint.id, datapoint.value_uint); @@ -389,7 +394,8 @@ void Tuya::set_datapoint_value(TuyaDatapoint datapoint) { buffer.push_back(data.size() >> 8); buffer.push_back(data.size() >> 0); buffer.insert(buffer.end(), data.begin(), data.end()); - this->send_command_(TuyaCommandType::DATAPOINT_DELIVER, buffer.data(), buffer.size()); + + this->send_command_(TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_DELIVER, .payload = buffer}); } void Tuya::register_listener(uint8_t datapoint_id, const std::function &func) { diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index ddbbb48edf3d..a2b4040eb3e5 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -61,6 +61,11 @@ enum class TuyaInitState : uint8_t { INIT_DONE, }; +struct TuyaCommand { + TuyaCommandType cmd; + std::vector payload; +}; + class Tuya : public Component, public uart::UARTDevice { public: float get_setup_priority() const override { return setup_priority::LATE; } @@ -82,9 +87,10 @@ class Tuya : public Component, public uart::UARTDevice { bool validate_message_(); void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len); - void send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len); - void send_empty_command_(TuyaCommandType command) { this->send_command_(command, nullptr, 0); } - void schedule_empty_command_(TuyaCommandType command); + void send_raw_command_(TuyaCommand command); + void process_command_queue_(); + void send_command_(TuyaCommand command); + void send_empty_command_(TuyaCommandType command); #ifdef USE_TIME optional time_id_{}; @@ -98,6 +104,7 @@ class Tuya : public Component, public uart::UARTDevice { std::vector datapoints_; std::vector rx_message_; std::vector ignore_mcu_update_on_datapoints_{}; + std::vector command_queue_; }; } // namespace tuya diff --git a/esphome/components/tx20/sensor.py b/esphome/components/tx20/sensor.py index 3547cdf50c35..434257470b99 100644 --- a/esphome/components/tx20/sensor.py +++ b/esphome/components/tx20/sensor.py @@ -2,22 +2,35 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor -from esphome.const import CONF_ID, CONF_WIND_SPEED, CONF_PIN, \ - CONF_WIND_DIRECTION_DEGREES, UNIT_KILOMETER_PER_HOUR, \ - ICON_WEATHER_WINDY, ICON_SIGN_DIRECTION, UNIT_DEGREES +from esphome.const import ( + CONF_ID, + CONF_WIND_SPEED, + CONF_PIN, + CONF_WIND_DIRECTION_DEGREES, + DEVICE_CLASS_EMPTY, + UNIT_KILOMETER_PER_HOUR, + ICON_WEATHER_WINDY, + ICON_SIGN_DIRECTION, + UNIT_DEGREES, +) -tx20_ns = cg.esphome_ns.namespace('tx20') -Tx20Component = tx20_ns.class_('Tx20Component', cg.Component) +tx20_ns = cg.esphome_ns.namespace("tx20") +Tx20Component = tx20_ns.class_("Tx20Component", cg.Component) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(Tx20Component), - cv.Optional(CONF_WIND_SPEED): - sensor.sensor_schema(UNIT_KILOMETER_PER_HOUR, ICON_WEATHER_WINDY, 1), - cv.Optional(CONF_WIND_DIRECTION_DEGREES): - sensor.sensor_schema(UNIT_DEGREES, ICON_SIGN_DIRECTION, 1), - cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema, - pins.validate_has_interrupt), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(Tx20Component), + cv.Optional(CONF_WIND_SPEED): sensor.sensor_schema( + UNIT_KILOMETER_PER_HOUR, ICON_WEATHER_WINDY, 1, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_WIND_DIRECTION_DEGREES): sensor.sensor_schema( + UNIT_DEGREES, ICON_SIGN_DIRECTION, 1, DEVICE_CLASS_EMPTY + ), + cv.Required(CONF_PIN): cv.All( + pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index b83aedad9f07..a02ea58def0c 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -1,26 +1,36 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins, automation -from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_RX_PIN, CONF_TX_PIN, CONF_UART_ID, \ - CONF_DATA, CONF_RX_BUFFER_SIZE +from esphome.const import ( + CONF_BAUD_RATE, + CONF_ID, + CONF_RX_PIN, + CONF_TX_PIN, + CONF_UART_ID, + CONF_DATA, + CONF_RX_BUFFER_SIZE, + CONF_INVERT, +) from esphome.core import CORE, coroutine -CODEOWNERS = ['@esphome/core'] -uart_ns = cg.esphome_ns.namespace('uart') -UARTComponent = uart_ns.class_('UARTComponent', cg.Component) -UARTDevice = uart_ns.class_('UARTDevice') -UARTWriteAction = uart_ns.class_('UARTWriteAction', automation.Action) +CODEOWNERS = ["@esphome/core"] +uart_ns = cg.esphome_ns.namespace("uart") +UARTComponent = uart_ns.class_("UARTComponent", cg.Component) +UARTDevice = uart_ns.class_("UARTDevice") +UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) MULTI_CONF = True def validate_raw_data(value): if isinstance(value, str): - return value.encode('utf-8') + return value.encode("utf-8") if isinstance(value, str): return value if isinstance(value, list): return cv.Schema([cv.hex_uint8_t])(value) - raise cv.Invalid("data must either be a string wrapped in quotes or a list of bytes") + raise cv.Invalid( + "data must either be a string wrapped in quotes or a list of bytes" + ) def validate_rx_pin(value): @@ -30,27 +40,37 @@ def validate_rx_pin(value): return value -UARTParityOptions = uart_ns.enum('UARTParityOptions') +UARTParityOptions = uart_ns.enum("UARTParityOptions") UART_PARITY_OPTIONS = { - 'NONE': UARTParityOptions.UART_CONFIG_PARITY_NONE, - 'EVEN': UARTParityOptions.UART_CONFIG_PARITY_EVEN, - 'ODD': UARTParityOptions.UART_CONFIG_PARITY_ODD, + "NONE": UARTParityOptions.UART_CONFIG_PARITY_NONE, + "EVEN": UARTParityOptions.UART_CONFIG_PARITY_EVEN, + "ODD": UARTParityOptions.UART_CONFIG_PARITY_ODD, } -CONF_STOP_BITS = 'stop_bits' -CONF_DATA_BITS = 'data_bits' -CONF_PARITY = 'parity' - -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(UARTComponent), - cv.Required(CONF_BAUD_RATE): cv.int_range(min=1), - cv.Optional(CONF_TX_PIN): pins.output_pin, - cv.Optional(CONF_RX_PIN): validate_rx_pin, - cv.Optional(CONF_RX_BUFFER_SIZE, default=256): cv.validate_bytes, - cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True), - cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8), - cv.Optional(CONF_PARITY, default="NONE"): cv.enum(UART_PARITY_OPTIONS, upper=True) -}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN)) +CONF_STOP_BITS = "stop_bits" +CONF_DATA_BITS = "data_bits" +CONF_PARITY = "parity" + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(UARTComponent), + cv.Required(CONF_BAUD_RATE): cv.int_range(min=1), + cv.Optional(CONF_TX_PIN): pins.output_pin, + cv.Optional(CONF_RX_PIN): validate_rx_pin, + cv.Optional(CONF_RX_BUFFER_SIZE, default=256): cv.validate_bytes, + cv.SplitDefault(CONF_INVERT, esp32=False): cv.All( + cv.only_on_esp32, cv.boolean + ), + cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True), + cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8), + cv.Optional(CONF_PARITY, default="NONE"): cv.enum( + UART_PARITY_OPTIONS, upper=True + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), +) def to_code(config): @@ -65,15 +85,19 @@ def to_code(config): if CONF_RX_PIN in config: cg.add(var.set_rx_pin(config[CONF_RX_PIN])) cg.add(var.set_rx_buffer_size(config[CONF_RX_BUFFER_SIZE])) + if CONF_INVERT in config: + cg.add(var.set_invert(config[CONF_INVERT])) cg.add(var.set_stop_bits(config[CONF_STOP_BITS])) cg.add(var.set_data_bits(config[CONF_DATA_BITS])) cg.add(var.set_parity(config[CONF_PARITY])) # A schema to use for all UART devices, all UART integrations must extend this! -UART_DEVICE_SCHEMA = cv.Schema({ - cv.GenerateID(CONF_UART_ID): cv.use_id(UARTComponent), -}) +UART_DEVICE_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_UART_ID): cv.use_id(UARTComponent), + } +) @coroutine @@ -86,10 +110,17 @@ def register_uart_device(var, config): cg.add(var.set_uart_parent(parent)) -@automation.register_action('uart.write', UARTWriteAction, cv.maybe_simple_value({ - cv.GenerateID(): cv.use_id(UARTComponent), - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), -}, key=CONF_DATA)) +@automation.register_action( + "uart.write", + UARTWriteAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(UARTComponent), + cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + }, + key=CONF_DATA, + ), +) def uart_write_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) yield cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/uart/switch/__init__.py b/esphome/components/uart/switch/__init__.py index 6cc11d8bbe16..e84035aa3e3b 100644 --- a/esphome/components/uart/switch/__init__.py +++ b/esphome/components/uart/switch/__init__.py @@ -1,20 +1,29 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch, uart -from esphome.const import CONF_DATA, CONF_ID, CONF_INVERTED +from esphome.const import CONF_DATA, CONF_ID, CONF_INVERTED, CONF_SEND_EVERY from esphome.core import HexInt from .. import uart_ns, validate_raw_data -DEPENDENCIES = ['uart'] +DEPENDENCIES = ["uart"] -UARTSwitch = uart_ns.class_('UARTSwitch', switch.Switch, uart.UARTDevice, cg.Component) +UARTSwitch = uart_ns.class_("UARTSwitch", switch.Switch, uart.UARTDevice, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(UARTSwitch), - cv.Required(CONF_DATA): validate_raw_data, - cv.Optional(CONF_INVERTED): cv.invalid("UART switches do not support inverted mode!"), -}).extend(uart.UART_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(UARTSwitch), + cv.Required(CONF_DATA): validate_raw_data, + cv.Optional(CONF_INVERTED): cv.invalid( + "UART switches do not support inverted mode!" + ), + cv.Optional(CONF_SEND_EVERY): cv.positive_time_period_milliseconds, + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): @@ -27,3 +36,6 @@ def to_code(config): if isinstance(data, bytes): data = [HexInt(x) for x in data] cg.add(var.set_data(data)) + + if CONF_SEND_EVERY in config: + cg.add(var.set_send_every(config[CONF_SEND_EVERY])) diff --git a/esphome/components/uart/switch/uart_switch.cpp b/esphome/components/uart/switch/uart_switch.cpp index 9974ee11792f..a1ebb6dbb9b6 100644 --- a/esphome/components/uart/switch/uart_switch.cpp +++ b/esphome/components/uart/switch/uart_switch.cpp @@ -6,6 +6,21 @@ namespace uart { static const char *TAG = "uart.switch"; +void UARTSwitch::loop() { + if (this->state && this->send_every_) { + const uint32_t now = millis(); + if (now - this->last_transmission_ > this->send_every_) { + this->write_command_(); + this->last_transmission_ = now; + } + } +} + +void UARTSwitch::write_command_() { + ESP_LOGD(TAG, "'%s': Sending data...", this->get_name().c_str()); + this->write_array(this->data_.data(), this->data_.size()); +} + void UARTSwitch::write_state(bool state) { if (!state) { this->publish_state(false); @@ -13,11 +28,20 @@ void UARTSwitch::write_state(bool state) { } this->publish_state(true); - ESP_LOGD(TAG, "'%s': Sending data...", this->get_name().c_str()); - this->write_array(this->data_.data(), this->data_.size()); - this->publish_state(false); + this->write_command_(); + + if (this->send_every_ == 0) { + this->publish_state(false); + } else { + this->last_transmission_ = millis(); + } +} +void UARTSwitch::dump_config() { + LOG_SWITCH("", "UART Switch", this); + if (this->send_every_) { + ESP_LOGCONFIG(TAG, " Send Every: %u", this->send_every_); + } } -void UARTSwitch::dump_config() { LOG_SWITCH("", "UART Switch", this); } } // namespace uart } // namespace esphome diff --git a/esphome/components/uart/switch/uart_switch.h b/esphome/components/uart/switch/uart_switch.h index c8a1b0d8c5b8..4c82d5680a2b 100644 --- a/esphome/components/uart/switch/uart_switch.h +++ b/esphome/components/uart/switch/uart_switch.h @@ -9,13 +9,19 @@ namespace uart { class UARTSwitch : public switch_::Switch, public UARTDevice, public Component { public: + void loop() override; + void set_data(const std::vector &data) { data_ = data; } + void set_send_every(uint32_t send_every) { this->send_every_ = send_every; } void dump_config() override; protected: + void write_command_(); void write_state(bool state) override; std::vector data_; + uint32_t send_every_; + uint32_t last_transmission_; }; } // namespace uart diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 7430a4ee056a..6dd62070dac8 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -90,6 +90,9 @@ class UARTComponent : public Component, public Stream { void set_tx_pin(uint8_t tx_pin) { this->tx_pin_ = tx_pin; } void set_rx_pin(uint8_t rx_pin) { this->rx_pin_ = rx_pin; } void set_rx_buffer_size(size_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; } +#ifdef ARDUINO_ARCH_ESP32 + void set_invert(bool invert) { this->invert_ = invert; } +#endif void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; } void set_data_bits(uint8_t data_bits) { this->data_bits_ = data_bits; } void set_parity(UARTParityOptions parity) { this->parity_ = parity; } @@ -106,6 +109,9 @@ class UARTComponent : public Component, public Stream { optional tx_pin_; optional rx_pin_; size_t rx_buffer_size_; +#ifdef ARDUINO_ARCH_ESP32 + bool invert_; +#endif uint32_t baud_rate_; uint8_t stop_bits_; uint8_t data_bits_; diff --git a/esphome/components/uart/uart_esp32.cpp b/esphome/components/uart/uart_esp32.cpp index f7af85cf7bed..920fe660be8a 100644 --- a/esphome/components/uart/uart_esp32.cpp +++ b/esphome/components/uart/uart_esp32.cpp @@ -80,7 +80,7 @@ void UARTComponent::setup() { } int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; - this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx); + this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, this->invert_); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); } diff --git a/esphome/components/uln2003/stepper.py b/esphome/components/uln2003/stepper.py index 278fcf67ebcf..4d2e5ab51847 100644 --- a/esphome/components/uln2003/stepper.py +++ b/esphome/components/uln2003/stepper.py @@ -2,29 +2,40 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import stepper -from esphome.const import CONF_ID, CONF_PIN_A, CONF_PIN_B, CONF_PIN_C, CONF_PIN_D, \ - CONF_SLEEP_WHEN_DONE, CONF_STEP_MODE - -uln2003_ns = cg.esphome_ns.namespace('uln2003') -ULN2003StepMode = uln2003_ns.enum('ULN2003StepMode') +from esphome.const import ( + CONF_ID, + CONF_PIN_A, + CONF_PIN_B, + CONF_PIN_C, + CONF_PIN_D, + CONF_SLEEP_WHEN_DONE, + CONF_STEP_MODE, +) + +uln2003_ns = cg.esphome_ns.namespace("uln2003") +ULN2003StepMode = uln2003_ns.enum("ULN2003StepMode") STEP_MODES = { - 'FULL_STEP': ULN2003StepMode.ULN2003_STEP_MODE_FULL_STEP, - 'HALF_STEP': ULN2003StepMode.ULN2003_STEP_MODE_HALF_STEP, - 'WAVE_DRIVE': ULN2003StepMode.ULN2003_STEP_MODE_WAVE_DRIVE, + "FULL_STEP": ULN2003StepMode.ULN2003_STEP_MODE_FULL_STEP, + "HALF_STEP": ULN2003StepMode.ULN2003_STEP_MODE_HALF_STEP, + "WAVE_DRIVE": ULN2003StepMode.ULN2003_STEP_MODE_WAVE_DRIVE, } -ULN2003 = uln2003_ns.class_('ULN2003', stepper.Stepper, cg.Component) - -CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({ - cv.Required(CONF_ID): cv.declare_id(ULN2003), - cv.Required(CONF_PIN_A): pins.gpio_output_pin_schema, - cv.Required(CONF_PIN_B): pins.gpio_output_pin_schema, - cv.Required(CONF_PIN_C): pins.gpio_output_pin_schema, - cv.Required(CONF_PIN_D): pins.gpio_output_pin_schema, - cv.Optional(CONF_SLEEP_WHEN_DONE, default=False): cv.boolean, - cv.Optional(CONF_STEP_MODE, default='FULL_STEP'): cv.enum(STEP_MODES, upper=True, space='_') -}).extend(cv.COMPONENT_SCHEMA) +ULN2003 = uln2003_ns.class_("ULN2003", stepper.Stepper, cg.Component) + +CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(ULN2003), + cv.Required(CONF_PIN_A): pins.gpio_output_pin_schema, + cv.Required(CONF_PIN_B): pins.gpio_output_pin_schema, + cv.Required(CONF_PIN_C): pins.gpio_output_pin_schema, + cv.Required(CONF_PIN_D): pins.gpio_output_pin_schema, + cv.Optional(CONF_SLEEP_WHEN_DONE, default=False): cv.boolean, + cv.Optional(CONF_STEP_MODE, default="FULL_STEP"): cv.enum( + STEP_MODES, upper=True, space="_" + ), + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/ultrasonic/__init__.py b/esphome/components/ultrasonic/__init__.py index 6f14e1003304..71a87b6ae508 100644 --- a/esphome/components/ultrasonic/__init__.py +++ b/esphome/components/ultrasonic/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@OttoWinter'] +CODEOWNERS = ["@OttoWinter"] diff --git a/esphome/components/ultrasonic/sensor.py b/esphome/components/ultrasonic/sensor.py index e4364f271c23..d5d8dec6f411 100644 --- a/esphome/components/ultrasonic/sensor.py +++ b/esphome/components/ultrasonic/sensor.py @@ -2,28 +2,45 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor -from esphome.const import CONF_ECHO_PIN, CONF_ID, CONF_TRIGGER_PIN, \ - CONF_TIMEOUT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL - -CONF_PULSE_TIME = 'pulse_time' - -ultrasonic_ns = cg.esphome_ns.namespace('ultrasonic') -UltrasonicSensorComponent = ultrasonic_ns.class_('UltrasonicSensorComponent', - sensor.Sensor, cg.PollingComponent) - -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, 2).extend({ - cv.GenerateID(): cv.declare_id(UltrasonicSensorComponent), - cv.Required(CONF_TRIGGER_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema, - - cv.Optional(CONF_TIMEOUT, default='2m'): cv.distance, - cv.Optional(CONF_PULSE_TIME, default='10us'): cv.positive_time_period_microseconds, - - cv.Optional('timeout_meter'): cv.invalid("The timeout_meter option has been renamed " - "to 'timeout' in 1.12."), - cv.Optional('timeout_time'): cv.invalid("The timeout_time option has been removed. Please " - "use 'timeout' in 1.12."), -}).extend(cv.polling_component_schema('60s')) +from esphome.const import ( + CONF_ECHO_PIN, + CONF_ID, + CONF_TRIGGER_PIN, + CONF_TIMEOUT, + DEVICE_CLASS_EMPTY, + UNIT_METER, + ICON_ARROW_EXPAND_VERTICAL, +) + +CONF_PULSE_TIME = "pulse_time" + +ultrasonic_ns = cg.esphome_ns.namespace("ultrasonic") +UltrasonicSensorComponent = ultrasonic_ns.class_( + "UltrasonicSensorComponent", sensor.Sensor, cg.PollingComponent +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, 2, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(UltrasonicSensorComponent), + cv.Required(CONF_TRIGGER_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_TIMEOUT, default="2m"): cv.distance, + cv.Optional( + CONF_PULSE_TIME, default="10us" + ): cv.positive_time_period_microseconds, + cv.Optional("timeout_meter"): cv.invalid( + "The timeout_meter option has been renamed " "to 'timeout' in 1.12." + ), + cv.Optional("timeout_time"): cv.invalid( + "The timeout_time option has been removed. Please " + "use 'timeout' in 1.12." + ), + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor.py index 1dacc996534c..c2e35ddfef62 100644 --- a/esphome/components/uptime/sensor.py +++ b/esphome/components/uptime/sensor.py @@ -1,14 +1,20 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import CONF_ID, UNIT_SECOND, ICON_TIMER +from esphome.const import CONF_ID, DEVICE_CLASS_EMPTY, UNIT_SECOND, ICON_TIMER -uptime_ns = cg.esphome_ns.namespace('uptime') -UptimeSensor = uptime_ns.class_('UptimeSensor', sensor.Sensor, cg.PollingComponent) +uptime_ns = cg.esphome_ns.namespace("uptime") +UptimeSensor = uptime_ns.class_("UptimeSensor", sensor.Sensor, cg.PollingComponent) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_SECOND, ICON_TIMER, 0).extend({ - cv.GenerateID(): cv.declare_id(UptimeSensor), -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_SECOND, ICON_TIMER, 0, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(UptimeSensor), + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): diff --git a/esphome/components/version/__init__.py b/esphome/components/version/__init__.py index 63db7aee2ecd..f70ffa952087 100644 --- a/esphome/components/version/__init__.py +++ b/esphome/components/version/__init__.py @@ -1 +1 @@ -CODEOWNERS = ['@esphome/core'] +CODEOWNERS = ["@esphome/core"] diff --git a/esphome/components/version/text_sensor.py b/esphome/components/version/text_sensor.py index 01cf8ba30bc0..711800136ce8 100644 --- a/esphome/components/version/text_sensor.py +++ b/esphome/components/version/text_sensor.py @@ -3,14 +3,18 @@ from esphome.components import text_sensor from esphome.const import CONF_ID, CONF_ICON, ICON_NEW_BOX, CONF_HIDE_TIMESTAMP -version_ns = cg.esphome_ns.namespace('version') -VersionTextSensor = version_ns.class_('VersionTextSensor', text_sensor.TextSensor, cg.Component) +version_ns = cg.esphome_ns.namespace("version") +VersionTextSensor = version_ns.class_( + "VersionTextSensor", text_sensor.TextSensor, cg.Component +) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(VersionTextSensor), - cv.Optional(CONF_ICON, default=ICON_NEW_BOX): text_sensor.icon, - cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(VersionTextSensor), + cv.Optional(CONF_ICON, default=ICON_NEW_BOX): text_sensor.icon, + cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean, + } +).extend(cv.COMPONENT_SCHEMA) def to_code(config): diff --git a/esphome/components/vl53l0x/sensor.py b/esphome/components/vl53l0x/sensor.py index 209016fe4094..309d4cf8b3bc 100644 --- a/esphome/components/vl53l0x/sensor.py +++ b/esphome/components/vl53l0x/sensor.py @@ -1,23 +1,61 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor -from esphome.const import CONF_ID, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_EMPTY, + UNIT_METER, + ICON_ARROW_EXPAND_VERTICAL, + CONF_ADDRESS, + CONF_TIMEOUT, + CONF_ENABLE_PIN, +) +from esphome import pins -DEPENDENCIES = ['i2c'] +DEPENDENCIES = ["i2c"] -vl53l0x_ns = cg.esphome_ns.namespace('vl53l0x') -VL53L0XSensor = vl53l0x_ns.class_('VL53L0XSensor', sensor.Sensor, cg.PollingComponent, - i2c.I2CDevice) +vl53l0x_ns = cg.esphome_ns.namespace("vl53l0x") +VL53L0XSensor = vl53l0x_ns.class_( + "VL53L0XSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) -CONF_SIGNAL_RATE_LIMIT = 'signal_rate_limit' -CONF_LONG_RANGE = 'long_range' +CONF_SIGNAL_RATE_LIMIT = "signal_rate_limit" +CONF_LONG_RANGE = "long_range" -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, 2).extend({ - cv.GenerateID(): cv.declare_id(VL53L0XSensor), - cv.Optional(CONF_SIGNAL_RATE_LIMIT, default=0.25): cv.float_range( - min=0.0, max=512.0, min_included=False, max_included=False), - cv.Optional(CONF_LONG_RANGE, default=False): cv.boolean, -}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x29)) + +def check_keys(obj): + if obj[CONF_ADDRESS] != 0x29 and CONF_ENABLE_PIN not in obj: + msg = "Address other then 0x29 requires enable_pin definition to allow sensor\r" + msg += "re-addressing. Also if you have more then one VL53 device on the same\r" + msg += "i2c bus, then all VL53 devices must have enable_pin defined." + raise cv.Invalid(msg) + return obj + + +def check_timeout(value): + value = cv.positive_time_period_microseconds(value) + if value.total_seconds > 60: + raise cv.Invalid("Maximum timeout can not be greater then 60 seconds") + return value + + +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema(UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, 2, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(VL53L0XSensor), + cv.Optional(CONF_SIGNAL_RATE_LIMIT, default=0.25): cv.float_range( + min=0.0, max=512.0, min_included=False, max_included=False + ), + cv.Optional(CONF_LONG_RANGE, default=False): cv.boolean, + cv.Optional(CONF_TIMEOUT, default="10ms"): check_timeout, + cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x29)), + check_keys, +) def to_code(config): @@ -25,5 +63,11 @@ def to_code(config): yield cg.register_component(var, config) cg.add(var.set_signal_rate_limit(config[CONF_SIGNAL_RATE_LIMIT])) cg.add(var.set_long_range(config[CONF_LONG_RANGE])) + cg.add(var.set_timeout_us(config[CONF_TIMEOUT])) + + if CONF_ENABLE_PIN in config: + enable = yield cg.gpio_pin_expression(config[CONF_ENABLE_PIN]) + cg.add(var.set_enable_pin(enable)) + yield sensor.register_sensor(var, config) yield i2c.register_i2c_device(var, config) diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.cpp b/esphome/components/vl53l0x/vl53l0x_sensor.cpp index 8ce822352faf..1926f7d11327 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.cpp +++ b/esphome/components/vl53l0x/vl53l0x_sensor.cpp @@ -14,20 +14,55 @@ namespace esphome { namespace vl53l0x { static const char *TAG = "vl53l0x"; +std::list VL53L0XSensor::vl53_sensors; +bool VL53L0XSensor::enable_pin_setup_complete = false; + +VL53L0XSensor::VL53L0XSensor() { VL53L0XSensor::vl53_sensors.push_back(this); } void VL53L0XSensor::dump_config() { LOG_SENSOR("", "VL53L0X", this); LOG_UPDATE_INTERVAL(this); LOG_I2C_DEVICE(this); + if (this->enable_pin_ != nullptr) { + LOG_PIN(" Enable Pin: ", this->enable_pin_); + } + ESP_LOGCONFIG(TAG, " Timeout: %u%s", this->timeout_us_, this->timeout_us_ > 0 ? "us" : " (no timeout)"); } + void VL53L0XSensor::setup() { + ESP_LOGD(TAG, "'%s' - setup BEGIN", this->name_.c_str()); + + if (!esphome::vl53l0x::VL53L0XSensor::enable_pin_setup_complete) { + for (auto &vl53_sensor : vl53_sensors) { + if (vl53_sensor->enable_pin_ != nullptr) { + // Disable the enable pin to force vl53 to HW Standby mode + ESP_LOGD(TAG, "i2c vl53l0x disable enable pins: GPIO%u", (vl53_sensor->enable_pin_)->get_pin()); + // Set enable pin as OUTPUT and disable the enable pin to force vl53 to HW Standby mode + vl53_sensor->enable_pin_->setup(); + vl53_sensor->enable_pin_->digital_write(false); + } + } + esphome::vl53l0x::VL53L0XSensor::enable_pin_setup_complete = true; + } + + if (this->enable_pin_ != nullptr) { + // Enable the enable pin to cause FW boot (to get back to 0x29 default address) + this->enable_pin_->digital_write(true); + delayMicroseconds(100); + } + + // Save the i2c address we want and force it to use the default 0x29 + // until we finish setup, then re-address to final desired address. + uint8_t final_address = address_; + this->set_i2c_address(0x29); + reg(0x89) |= 0x01; reg(0x88) = 0x00; reg(0x80) = 0x01; reg(0xFF) = 0x01; reg(0x00) = 0x00; - stop_variable_ = reg(0x91).get(); + this->stop_variable_ = reg(0x91).get(); reg(0x00) = 0x01; reg(0xFF) = 0x00; @@ -52,8 +87,15 @@ void VL53L0XSensor::setup() { reg(0x94) = 0x6B; reg(0x83) = 0x00; - while (reg(0x83).get() == 0x00) + this->timeout_start_us_ = micros(); + while (reg(0x83).get() == 0x00) { + if (this->timeout_us_ > 0 && ((uint16_t)(micros() - this->timeout_start_us_) > this->timeout_us_)) { + ESP_LOGE(TAG, "'%s' - setup timeout", this->name_.c_str()); + this->mark_failed(); + return; + } yield(); + } reg(0x83) = 0x01; uint8_t tmp = reg(0x92).get(); @@ -205,11 +247,22 @@ void VL53L0XSensor::setup() { return; } reg(0x01) = 0xE8; + + // Set the sensor to the desired final address + // The following is different for VL53L0X vs VL53L1X + // I2C_SXXXX_DEVICE_ADDRESS = 0x8A for VL53L0X + // I2C_SXXXX__DEVICE_ADDRESS = 0x0001 for VL53L1X + reg(0x8A) = final_address & 0x7F; + this->set_i2c_address(final_address); + + ESP_LOGD(TAG, "'%s' - setup END", this->name_.c_str()); } void VL53L0XSensor::update() { if (this->initiated_read_ || this->waiting_for_interrupt_) { this->publish_state(NAN); - this->status_set_warning(); + this->status_momentary_warning("update", 5000); + ESP_LOGW(TAG, "%s - update called before prior reading complete - initiated:%d waiting_for_interrupt:%d", + this->name_.c_str(), this->initiated_read_, this->waiting_for_interrupt_); } // initiate single shot measurement @@ -217,7 +270,7 @@ void VL53L0XSensor::update() { reg(0xFF) = 0x01; reg(0x00) = 0x00; - reg(0x91) = stop_variable_; + reg(0x91) = this->stop_variable_; reg(0x00) = 0x01; reg(0xFF) = 0x00; reg(0x80) = 0x00; @@ -246,7 +299,7 @@ void VL53L0XSensor::loop() { this->waiting_for_interrupt_ = false; if (range_mm >= 8190) { - ESP_LOGW(TAG, "'%s' - Distance is out of range, please move the target closer", this->name_.c_str()); + ESP_LOGD(TAG, "'%s' - Distance is out of range, please move the target closer", this->name_.c_str()); this->publish_state(NAN); return; } diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.h b/esphome/components/vl53l0x/vl53l0x_sensor.h index 4939a9806c93..2662b768aee1 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.h +++ b/esphome/components/vl53l0x/vl53l0x_sensor.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" @@ -20,6 +22,8 @@ struct SequenceStepTimeouts { class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { public: + VL53L0XSensor(); + void setup() override; void dump_config() override; @@ -30,6 +34,8 @@ class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c void set_signal_rate_limit(float signal_rate_limit) { signal_rate_limit_ = signal_rate_limit; } void set_long_range(bool long_range) { long_range_ = long_range; } + void set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; } + void set_enable_pin(GPIOPin *enable) { this->enable_pin_ = enable; } protected: uint32_t get_measurement_timing_budget_() { @@ -249,10 +255,17 @@ class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c float signal_rate_limit_; bool long_range_; + GPIOPin *enable_pin_{nullptr}; uint32_t measurement_timing_budget_us_; bool initiated_read_{false}; bool waiting_for_interrupt_{false}; uint8_t stop_variable_; + + uint16_t timeout_start_us_; + uint16_t timeout_us_{}; + + static std::list vl53_sensors; + static bool enable_pin_setup_complete; }; } // namespace vl53l0x diff --git a/esphome/components/voltage_sampler/__init__.py b/esphome/components/voltage_sampler/__init__.py index 64161205d8a7..e60918096ef9 100644 --- a/esphome/components/voltage_sampler/__init__.py +++ b/esphome/components/voltage_sampler/__init__.py @@ -1,4 +1,4 @@ import esphome.codegen as cg -voltage_sampler_ns = cg.esphome_ns.namespace('voltage_sampler') -VoltageSampler = voltage_sampler_ns.class_('VoltageSampler') +voltage_sampler_ns = cg.esphome_ns.namespace("voltage_sampler") +VoltageSampler = voltage_sampler_ns.class_("VoltageSampler") diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index fcbbc0a5004c..16f234563c2a 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -2,68 +2,100 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display, spi -from esphome.const import CONF_BUSY_PIN, CONF_DC_PIN, CONF_FULL_UPDATE_EVERY, \ - CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_PAGES, CONF_RESET_PIN +from esphome.const import ( + CONF_BUSY_PIN, + CONF_DC_PIN, + CONF_FULL_UPDATE_EVERY, + CONF_ID, + CONF_LAMBDA, + CONF_MODEL, + CONF_PAGES, + CONF_RESET_PIN, +) -DEPENDENCIES = ['spi'] +DEPENDENCIES = ["spi"] -waveshare_epaper_ns = cg.esphome_ns.namespace('waveshare_epaper') -WaveshareEPaper = waveshare_epaper_ns.class_('WaveshareEPaper', cg.PollingComponent, spi.SPIDevice, - display.DisplayBuffer) -WaveshareEPaperTypeA = waveshare_epaper_ns.class_('WaveshareEPaperTypeA', WaveshareEPaper) -WaveshareEPaper2P7In = waveshare_epaper_ns.class_('WaveshareEPaper2P7In', WaveshareEPaper) -WaveshareEPaper2P9InB = waveshare_epaper_ns.class_('WaveshareEPaper2P9InB', WaveshareEPaper) -WaveshareEPaper4P2In = waveshare_epaper_ns.class_('WaveshareEPaper4P2In', WaveshareEPaper) -WaveshareEPaper5P8In = waveshare_epaper_ns.class_('WaveshareEPaper5P8In', WaveshareEPaper) -WaveshareEPaper7P5In = waveshare_epaper_ns.class_('WaveshareEPaper7P5In', WaveshareEPaper) -WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_('WaveshareEPaper7P5InV2', WaveshareEPaper) +waveshare_epaper_ns = cg.esphome_ns.namespace("waveshare_epaper") +WaveshareEPaper = waveshare_epaper_ns.class_( + "WaveshareEPaper", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer +) +WaveshareEPaperTypeA = waveshare_epaper_ns.class_( + "WaveshareEPaperTypeA", WaveshareEPaper +) +WaveshareEPaper2P7In = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7In", WaveshareEPaper +) +WaveshareEPaper2P9InB = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InB", WaveshareEPaper +) +WaveshareEPaper4P2In = waveshare_epaper_ns.class_( + "WaveshareEPaper4P2In", WaveshareEPaper +) +WaveshareEPaper5P8In = waveshare_epaper_ns.class_( + "WaveshareEPaper5P8In", WaveshareEPaper +) +WaveshareEPaper7P5In = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5In", WaveshareEPaper +) +WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InV2", WaveshareEPaper +) -WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum('WaveshareEPaperTypeAModel') -WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum('WaveshareEPaperTypeBModel') +WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel") +WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel") MODELS = { - '1.54in': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN), - '2.13in': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN), - '2.13in-ttgo': ('a', WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN), - '2.13in-ttgo-b73': ('a', WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B73), - '2.90in': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN), - '2.90inv2': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2), - '2.70in': ('b', WaveshareEPaper2P7In), - '2.90in-b': ('b', WaveshareEPaper2P9InB), - '4.20in': ('b', WaveshareEPaper4P2In), - '5.83in': ('b', WaveshareEPaper5P8In), - '7.50in': ('b', WaveshareEPaper7P5In), - '7.50inv2': ('b', WaveshareEPaper7P5InV2), + "1.54in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN), + "2.13in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN), + "2.13in-ttgo": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN), + "2.13in-ttgo-b1": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B1), + "2.13in-ttgo-b73": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B73), + "2.90in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN), + "2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2), + "2.70in": ("b", WaveshareEPaper2P7In), + "2.90in-b": ("b", WaveshareEPaper2P9InB), + "4.20in": ("b", WaveshareEPaper4P2In), + "5.83in": ("b", WaveshareEPaper5P8In), + "7.50in": ("b", WaveshareEPaper7P5In), + "7.50inv2": ("b", WaveshareEPaper7P5InV2), } def validate_full_update_every_only_type_a(value): if CONF_FULL_UPDATE_EVERY not in value: return value - if MODELS[value[CONF_MODEL]][0] != 'a': - raise cv.Invalid("The 'full_update_every' option is only available for models " - "'1.54in', '2.13in', '2.90in', and '2.90inV2'.") + if MODELS[value[CONF_MODEL]][0] != "a": + raise cv.Invalid( + "The 'full_update_every' option is only available for models " + "'1.54in', '2.13in', '2.90in', and '2.90inV2'." + ) return value -CONFIG_SCHEMA = cv.All(display.FULL_DISPLAY_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(WaveshareEPaper), - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True), - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema, - cv.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t, -}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema()), - validate_full_update_every_only_type_a, - cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WaveshareEPaper), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t, + } + ) + .extend(cv.polling_component_schema("1s")) + .extend(spi.spi_device_schema()), + validate_full_update_every_only_type_a, + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) def to_code(config): model_type, model = MODELS[config[CONF_MODEL]] - if model_type == 'a': + if model_type == "a": rhs = WaveshareEPaperTypeA.new(model) var = cg.Pvariable(config[CONF_ID], rhs, WaveshareEPaperTypeA) - elif model_type == 'b': + elif model_type == "b": rhs = model.new() var = cg.Pvariable(config[CONF_ID], rhs, model) else: @@ -77,8 +109,9 @@ def to_code(config): cg.add(var.set_dc_pin(dc)) if CONF_LAMBDA in config: - lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], - return_type=cg.void) + lambda_ = yield cg.process_lambda( + config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + ) cg.add(var.set_writer(lambda_)) if CONF_RESET_PIN in config: reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN]) diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index fa0cf6aa4f06..0fb783107db0 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -9,6 +9,7 @@ namespace waveshare_epaper { static const char *TAG = "waveshare_epaper"; static const uint8_t LUT_SIZE_WAVESHARE = 30; + static const uint8_t FULL_UPDATE_LUT[LUT_SIZE_WAVESHARE] = {0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, 0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51, 0x35, 0x51, 0x51, 0x19, 0x01, 0x00}; @@ -18,7 +19,6 @@ static const uint8_t PARTIAL_UPDATE_LUT[LUT_SIZE_WAVESHARE] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static const uint8_t LUT_SIZE_TTGO = 70; -static const uint8_t LUT_SIZE_TTGO_B73 = 100; static const uint8_t FULL_UPDATE_LUT_TTGO[LUT_SIZE_TTGO] = { 0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, // LUT0: BB: VS 0 ~7 @@ -35,6 +35,23 @@ static const uint8_t FULL_UPDATE_LUT_TTGO[LUT_SIZE_TTGO] = { 0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6 }; +static const uint8_t PARTIAL_UPDATE_LUT_TTGO[LUT_SIZE_TTGO] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT0: BB: VS 0 ~7 + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT1: BW: VS 0 ~7 + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT2: WB: VS 0 ~7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT3: WW: VS 0 ~7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT4: VCOM: VS 0 ~7 + 0x0A, 0x00, 0x00, 0x00, 0x00, // TP0 A~D RP0 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP1 A~D RP1 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP2 A~D RP2 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP3 A~D RP3 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP4 A~D RP4 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP5 A~D RP5 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6 +}; + +static const uint8_t LUT_SIZE_TTGO_B73 = 100; + static const uint8_t FULL_UPDATE_LUT_TTGO_B73[LUT_SIZE_TTGO_B73] = { 0xA0, 0x90, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x90, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x90, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x90, 0xA0, 0x00, @@ -55,20 +72,15 @@ static const uint8_t PARTIAL_UPDATE_LUT_TTGO_B73[LUT_SIZE_TTGO_B73] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -static const uint8_t PARTIAL_UPDATE_LUT_TTGO[LUT_SIZE_TTGO] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT0: BB: VS 0 ~7 - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT1: BW: VS 0 ~7 - 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT2: WB: VS 0 ~7 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT3: WW: VS 0 ~7 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT4: VCOM: VS 0 ~7 - 0x0A, 0x00, 0x00, 0x00, 0x00, // TP0 A~D RP0 - 0x00, 0x00, 0x00, 0x00, 0x00, // TP1 A~D RP1 - 0x00, 0x00, 0x00, 0x00, 0x00, // TP2 A~D RP2 - 0x00, 0x00, 0x00, 0x00, 0x00, // TP3 A~D RP3 - 0x00, 0x00, 0x00, 0x00, 0x00, // TP4 A~D RP4 - 0x00, 0x00, 0x00, 0x00, 0x00, // TP5 A~D RP5 - 0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6 -}; +static const uint8_t LUT_SIZE_TTGO_B1 = 29; + +static const uint8_t FULL_UPDATE_LUT_TTGO_B1[LUT_SIZE_TTGO_B1] = { + 0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x01, 0x00, 0x00, 0x00, 0x00}; + +static const uint8_t PARTIAL_UPDATE_LUT_TTGO_B1[LUT_SIZE_TTGO_B1] = { + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; void WaveshareEPaper::setup_pins_() { this->init_internal_(this->get_buffer_length_()); @@ -103,7 +115,7 @@ bool WaveshareEPaper::wait_until_idle_() { const uint32_t start = millis(); while (this->busy_pin_->digital_read()) { - if (millis() - start > 1000) { + if (millis() - start > this->idle_timeout_()) { ESP_LOGE(TAG, "Timeout while displaying image!"); return false; } @@ -177,13 +189,19 @@ void WaveshareEPaperTypeA::initialize() { // COMMAND DATA ENTRY MODE SETTING this->command(0x11); - this->data(0x03); // from top left to bottom right - - if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2) { - // RAM content option for Display Update - this->command(0x21); - this->data(0x00); - this->data(0x80); + switch (this->model_) { + case TTGO_EPAPER_2_13_IN_B1: + this->data(0x01); // x increase, y decrease : as in demo code + break; + case WAVESHARE_EPAPER_2_9_IN_V2: + this->data(0x03); // from top left to bottom right + // RAM content option for Display Update + this->command(0x21); + this->data(0x00); + this->data(0x80); + break; + default: + this->data(0x03); // from top left to bottom right } } void WaveshareEPaperTypeA::dump_config() { @@ -201,6 +219,9 @@ void WaveshareEPaperTypeA::dump_config() { case TTGO_EPAPER_2_13_IN_B73: ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO B73)"); break; + case TTGO_EPAPER_2_13_IN_B1: + ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO B1)"); + break; case WAVESHARE_EPAPER_2_9_IN: ESP_LOGCONFIG(TAG, " Model: 2.9in"); break; @@ -223,38 +244,69 @@ void HOT WaveshareEPaperTypeA::display() { return; } - if (this->full_update_every_ >= 2) { + if (this->full_update_every_ >= 1) { if (full_update != prev_full_update) { - if (this->model_ == TTGO_EPAPER_2_13_IN) { - this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO : PARTIAL_UPDATE_LUT_TTGO, LUT_SIZE_TTGO); - } else if (this->model_ == TTGO_EPAPER_2_13_IN_B73) { - this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO_B73 : PARTIAL_UPDATE_LUT_TTGO_B73, LUT_SIZE_TTGO_B73); - } else { - this->write_lut_(full_update ? FULL_UPDATE_LUT : PARTIAL_UPDATE_LUT, LUT_SIZE_WAVESHARE); + switch (this->model_) { + case TTGO_EPAPER_2_13_IN: + this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO : PARTIAL_UPDATE_LUT_TTGO, LUT_SIZE_TTGO); + break; + case TTGO_EPAPER_2_13_IN_B73: + this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO_B73 : PARTIAL_UPDATE_LUT_TTGO_B73, LUT_SIZE_TTGO_B73); + break; + case TTGO_EPAPER_2_13_IN_B1: + this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO_B1 : PARTIAL_UPDATE_LUT_TTGO_B1, LUT_SIZE_TTGO_B1); + break; + default: + this->write_lut_(full_update ? FULL_UPDATE_LUT : PARTIAL_UPDATE_LUT, LUT_SIZE_WAVESHARE); } } this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; } // Set x & y regions we want to write to (full) - // COMMAND SET RAM X ADDRESS START END POSITION - this->command(0x44); - this->data(0x00); - this->data((this->get_width_internal() - 1) >> 3); - // COMMAND SET RAM Y ADDRESS START END POSITION - this->command(0x45); - this->data(0x00); - this->data(0x00); - this->data(this->get_height_internal() - 1); - this->data((this->get_height_internal() - 1) >> 8); + switch (this->model_) { + case TTGO_EPAPER_2_13_IN_B1: + // COMMAND SET RAM X ADDRESS START END POSITION + this->command(0x44); + this->data(0x00); + this->data((this->get_width_internal() - 1) >> 3); + // COMMAND SET RAM Y ADDRESS START END POSITION + this->command(0x45); + this->data(this->get_height_internal() - 1); + this->data((this->get_height_internal() - 1) >> 8); + this->data(0x00); + this->data(0x00); + + // COMMAND SET RAM X ADDRESS COUNTER + this->command(0x4E); + this->data(0x00); + // COMMAND SET RAM Y ADDRESS COUNTER + this->command(0x4F); + this->data(this->get_height_internal() - 1); + this->data((this->get_height_internal() - 1) >> 8); - // COMMAND SET RAM X ADDRESS COUNTER - this->command(0x4E); - this->data(0x00); - // COMMAND SET RAM Y ADDRESS COUNTER - this->command(0x4F); - this->data(0x00); - this->data(0x00); + break; + + default: + // COMMAND SET RAM X ADDRESS START END POSITION + this->command(0x44); + this->data(0x00); + this->data((this->get_width_internal() - 1) >> 3); + // COMMAND SET RAM Y ADDRESS START END POSITION + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data(this->get_height_internal() - 1); + this->data((this->get_height_internal() - 1) >> 8); + + // COMMAND SET RAM X ADDRESS COUNTER + this->command(0x4E); + this->data(0x00); + // COMMAND SET RAM Y ADDRESS COUNTER + this->command(0x4F); + this->data(0x00); + this->data(0x00); + } if (!this->wait_until_idle_()) { this->status_set_warning(); @@ -264,13 +316,28 @@ void HOT WaveshareEPaperTypeA::display() { // COMMAND WRITE RAM this->command(0x24); this->start_data_(); - this->write_array(this->buffer_, this->get_buffer_length_()); + switch (this->model_) { + case TTGO_EPAPER_2_13_IN_B1: { // block needed because of variable initializations + int16_t wb = ((this->get_width_internal()) >> 3); + for (int i = 0; i < this->get_height_internal(); i++) { + for (int j = 0; j < wb; j++) { + int idx = j + (this->get_height_internal() - 1 - i) * wb; + this->write_byte(this->buffer_[idx]); + } + } + break; + } + default: + this->write_array(this->buffer_, this->get_buffer_length_()); + } this->end_data_(); // COMMAND DISPLAY UPDATE CONTROL 2 this->command(0x22); if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2) { this->data(full_update ? 0xF7 : 0xFF); + } else if (this->model_ == TTGO_EPAPER_2_13_IN_B73) { + this->data(0xC7); } else { this->data(0xC4); } @@ -292,6 +359,8 @@ int WaveshareEPaperTypeA::get_width_internal() { return 128; case TTGO_EPAPER_2_13_IN_B73: return 128; + case TTGO_EPAPER_2_13_IN_B1: + return 128; case WAVESHARE_EPAPER_2_9_IN: return 128; case WAVESHARE_EPAPER_2_9_IN_V2: @@ -309,6 +378,8 @@ int WaveshareEPaperTypeA::get_height_internal() { return 250; case TTGO_EPAPER_2_13_IN_B73: return 250; + case TTGO_EPAPER_2_13_IN_B1: + return 250; case WAVESHARE_EPAPER_2_9_IN: return 296; case WAVESHARE_EPAPER_2_9_IN_V2: @@ -327,6 +398,16 @@ void WaveshareEPaperTypeA::set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } +int WaveshareEPaperTypeA::idle_timeout_() { + switch (this->model_) { + case TTGO_EPAPER_2_13_IN_B1: + return 2500; + break; + default: + return WaveshareEPaper::idle_timeout_(); + } +} + // ======================================================== // Type B // ======================================================== diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 8ea73d053a22..0b8958e7f00b 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -61,6 +61,7 @@ class WaveshareEPaper : public PollingComponent, GPIOPin *reset_pin_{nullptr}; GPIOPin *dc_pin_; GPIOPin *busy_pin_{nullptr}; + virtual int idle_timeout_() { return 1000; } // NOLINT(readability-identifier-naming) }; enum WaveshareEPaperTypeAModel { @@ -70,6 +71,7 @@ enum WaveshareEPaperTypeAModel { WAVESHARE_EPAPER_2_9_IN_V2, TTGO_EPAPER_2_13_IN, TTGO_EPAPER_2_13_IN_B73, + TTGO_EPAPER_2_13_IN_B1, }; class WaveshareEPaperTypeA : public WaveshareEPaper { @@ -106,6 +108,7 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { uint32_t full_update_every_{30}; uint32_t at_update_{0}; WaveshareEPaperTypeAModel model_; + int idle_timeout_() override; }; enum WaveshareEPaperTypeBModel { diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 069b0a389596..d04f2077f467 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -3,29 +3,46 @@ from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.const import ( - CONF_CSS_INCLUDE, CONF_CSS_URL, CONF_ID, CONF_JS_INCLUDE, CONF_JS_URL, CONF_PORT, - CONF_AUTH, CONF_USERNAME, CONF_PASSWORD) + CONF_CSS_INCLUDE, + CONF_CSS_URL, + CONF_ID, + CONF_JS_INCLUDE, + CONF_JS_URL, + CONF_PORT, + CONF_AUTH, + CONF_USERNAME, + CONF_PASSWORD, +) from esphome.core import coroutine_with_priority -AUTO_LOAD = ['json', 'web_server_base'] +AUTO_LOAD = ["json", "web_server_base"] -web_server_ns = cg.esphome_ns.namespace('web_server') -WebServer = web_server_ns.class_('WebServer', cg.Component, cg.Controller) +web_server_ns = cg.esphome_ns.namespace("web_server") +WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(WebServer), - cv.Optional(CONF_PORT, default=80): cv.port, - cv.Optional(CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css"): cv.string, - cv.Optional(CONF_CSS_INCLUDE): cv.file_, - cv.Optional(CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"): cv.string, - cv.Optional(CONF_JS_INCLUDE): cv.file_, - cv.Optional(CONF_AUTH): cv.Schema({ - cv.Required(CONF_USERNAME): cv.string_strict, - cv.Required(CONF_PASSWORD): cv.string_strict, - }), - - cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase), -}).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(WebServer), + cv.Optional(CONF_PORT, default=80): cv.port, + cv.Optional( + CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css" + ): cv.string, + cv.Optional(CONF_CSS_INCLUDE): cv.file_, + cv.Optional( + CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js" + ): cv.string, + cv.Optional(CONF_JS_INCLUDE): cv.file_, + cv.Optional(CONF_AUTH): cv.Schema( + { + cv.Required(CONF_USERNAME): cv.string_strict, + cv.Required(CONF_PASSWORD): cv.string_strict, + } + ), + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( + web_server_base.WebServerBase + ), + } +).extend(cv.COMPONENT_SCHEMA) @coroutine_with_priority(40.0) @@ -36,17 +53,17 @@ def to_code(config): yield cg.register_component(var, config) cg.add(paren.set_port(config[CONF_PORT])) - cg.add_define('WEBSERVER_PORT', config[CONF_PORT]) + cg.add_define("WEBSERVER_PORT", config[CONF_PORT]) cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) if CONF_AUTH in config: cg.add(var.set_username(config[CONF_AUTH][CONF_USERNAME])) cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD])) if CONF_CSS_INCLUDE in config: - cg.add_define('WEBSERVER_CSS_INCLUDE') + cg.add_define("WEBSERVER_CSS_INCLUDE") with open(config[CONF_CSS_INCLUDE], "r") as myfile: cg.add(var.set_css_include(myfile.read())) if CONF_JS_INCLUDE in config: - cg.add_define('WEBSERVER_JS_INCLUDE') + cg.add_define("WEBSERVER_JS_INCLUDE") with open(config[CONF_JS_INCLUDE], "r") as myfile: cg.add(var.set_js_include(myfile.read())) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 9e0e881b1b23..fbb215ee171f 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -12,6 +12,10 @@ #include #endif +#ifdef USE_FAN +#include "esphome/components/fan/fan_helpers.h" +#endif + namespace esphome { namespace web_server { @@ -364,8 +368,10 @@ std::string WebServer::fan_json(fan::FanState *obj) { root["id"] = "fan-" + obj->get_object_id(); root["state"] = obj->state ? "ON" : "OFF"; root["value"] = obj->state; - if (obj->get_traits().supports_speed()) { - switch (obj->speed) { + const auto traits = obj->get_traits(); + if (traits.supports_speed()) { + root["speed_level"] = obj->speed; + switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) { case fan::FAN_SPEED_LOW: root["speed"] = "low"; break; @@ -400,6 +406,15 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, UrlMatch matc String speed = request->getParam("speed")->value(); call.set_speed(speed.c_str()); } + if (request->hasParam("speed_level")) { + String speed_level = request->getParam("speed_level")->value(); + auto val = parse_int(speed_level.c_str()); + if (!val.has_value()) { + ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str()); + return; + } + call.set_speed(*val); + } if (request->hasParam("oscillation")) { String speed = request->getParam("oscillation")->value(); auto val = parse_on_off(speed.c_str()); diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 05f4a4a4c68b..09f5dacd7c52 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -3,17 +3,19 @@ from esphome.const import CONF_ID from esphome.core import coroutine_with_priority, CORE -CODEOWNERS = ['@OttoWinter'] -DEPENDENCIES = ['network'] -AUTO_LOAD = ['async_tcp'] +CODEOWNERS = ["@OttoWinter"] +DEPENDENCIES = ["network"] +AUTO_LOAD = ["async_tcp"] -web_server_base_ns = cg.esphome_ns.namespace('web_server_base') -WebServerBase = web_server_base_ns.class_('WebServerBase', cg.Component) +web_server_base_ns = cg.esphome_ns.namespace("web_server_base") +WebServerBase = web_server_base_ns.class_("WebServerBase", cg.Component) -CONF_WEB_SERVER_BASE_ID = 'web_server_base_id' -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(WebServerBase), -}) +CONF_WEB_SERVER_BASE_ID = "web_server_base_id" +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(WebServerBase), + } +) @coroutine_with_priority(65.0) @@ -22,6 +24,6 @@ def to_code(config): yield cg.register_component(var, config) if CORE.is_esp32: - cg.add_library('FS', None) + cg.add_library("FS", None) # https://github.com/OttoWinter/ESPAsyncWebServer/blob/master/library.json - cg.add_library('ESPAsyncWebServer-esphome', '1.2.7') + cg.add_library("ESPAsyncWebServer-esphome", "1.2.7") diff --git a/esphome/components/whirlpool/climate.py b/esphome/components/whirlpool/climate.py index 1fd62b411a00..d6d9f3e1114d 100644 --- a/esphome/components/whirlpool/climate.py +++ b/esphome/components/whirlpool/climate.py @@ -3,22 +3,24 @@ from esphome.components import climate_ir from esphome.const import CONF_ID, CONF_MODEL -AUTO_LOAD = ['climate_ir'] -CODEOWNERS = ['@glmnet'] +AUTO_LOAD = ["climate_ir"] +CODEOWNERS = ["@glmnet"] -whirlpool_ns = cg.esphome_ns.namespace('whirlpool') -WhirlpoolClimate = whirlpool_ns.class_('WhirlpoolClimate', climate_ir.ClimateIR) +whirlpool_ns = cg.esphome_ns.namespace("whirlpool") +WhirlpoolClimate = whirlpool_ns.class_("WhirlpoolClimate", climate_ir.ClimateIR) -Model = whirlpool_ns.enum('Model') +Model = whirlpool_ns.enum("Model") MODELS = { - 'DG11J1-3A': Model.MODEL_DG11J1_3A, - 'DG11J1-91': Model.MODEL_DG11J1_91, + "DG11J1-3A": Model.MODEL_DG11J1_3A, + "DG11J1-91": Model.MODEL_DG11J1_91, } -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(WhirlpoolClimate), - cv.Optional(CONF_MODEL, default='DG11J1-3A'): cv.enum(MODELS, upper=True) -}) +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WhirlpoolClimate), + cv.Optional(CONF_MODEL, default="DG11J1-3A"): cv.enum(MODELS, upper=True), + } +) def to_code(config): diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 4fe6929d7512..f5b7340ad605 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -2,31 +2,54 @@ import esphome.config_validation as cv from esphome import automation from esphome.automation import Condition -from esphome.const import CONF_AP, CONF_BSSID, CONF_CHANNEL, CONF_DNS1, CONF_DNS2, CONF_DOMAIN, \ - CONF_FAST_CONNECT, CONF_GATEWAY, CONF_HIDDEN, CONF_ID, CONF_MANUAL_IP, CONF_NETWORKS, \ - CONF_PASSWORD, CONF_POWER_SAVE_MODE, CONF_REBOOT_TIMEOUT, CONF_SSID, CONF_STATIC_IP, \ - CONF_SUBNET, CONF_USE_ADDRESS, CONF_PRIORITY, CONF_IDENTITY, CONF_CERTIFICATE_AUTHORITY, \ - CONF_CERTIFICATE, CONF_KEY, CONF_USERNAME, CONF_EAP +from esphome.const import ( + CONF_AP, + CONF_BSSID, + CONF_CHANNEL, + CONF_DNS1, + CONF_DNS2, + CONF_DOMAIN, + CONF_FAST_CONNECT, + CONF_GATEWAY, + CONF_HIDDEN, + CONF_ID, + CONF_MANUAL_IP, + CONF_NETWORKS, + CONF_PASSWORD, + CONF_POWER_SAVE_MODE, + CONF_REBOOT_TIMEOUT, + CONF_SSID, + CONF_STATIC_IP, + CONF_SUBNET, + CONF_USE_ADDRESS, + CONF_PRIORITY, + CONF_IDENTITY, + CONF_CERTIFICATE_AUTHORITY, + CONF_CERTIFICATE, + CONF_KEY, + CONF_USERNAME, + CONF_EAP, +) from esphome.core import CORE, HexInt, coroutine_with_priority from . import wpa2_eap -AUTO_LOAD = ['network'] +AUTO_LOAD = ["network"] -wifi_ns = cg.esphome_ns.namespace('wifi') -EAPAuth = wifi_ns.struct('EAPAuth') -IPAddress = cg.global_ns.class_('IPAddress') -ManualIP = wifi_ns.struct('ManualIP') -WiFiComponent = wifi_ns.class_('WiFiComponent', cg.Component) -WiFiAP = wifi_ns.struct('WiFiAP') +wifi_ns = cg.esphome_ns.namespace("wifi") +EAPAuth = wifi_ns.struct("EAPAuth") +IPAddress = cg.global_ns.class_("IPAddress") +ManualIP = wifi_ns.struct("ManualIP") +WiFiComponent = wifi_ns.class_("WiFiComponent", cg.Component) +WiFiAP = wifi_ns.struct("WiFiAP") -WiFiPowerSaveMode = wifi_ns.enum('WiFiPowerSaveMode') +WiFiPowerSaveMode = wifi_ns.enum("WiFiPowerSaveMode") WIFI_POWER_SAVE_MODES = { - 'NONE': WiFiPowerSaveMode.WIFI_POWER_SAVE_NONE, - 'LIGHT': WiFiPowerSaveMode.WIFI_POWER_SAVE_LIGHT, - 'HIGH': WiFiPowerSaveMode.WIFI_POWER_SAVE_HIGH, + "NONE": WiFiPowerSaveMode.WIFI_POWER_SAVE_NONE, + "LIGHT": WiFiPowerSaveMode.WIFI_POWER_SAVE_LIGHT, + "HIGH": WiFiPowerSaveMode.WIFI_POWER_SAVE_HIGH, } -WiFiConnectedCondition = wifi_ns.class_('WiFiConnectedCondition', Condition) +WiFiConnectedCondition = wifi_ns.class_("WiFiConnectedCondition", Condition) def validate_password(value): @@ -49,47 +72,67 @@ def validate_channel(value): return value -AP_MANUAL_IP_SCHEMA = cv.Schema({ - cv.Required(CONF_STATIC_IP): cv.ipv4, - cv.Required(CONF_GATEWAY): cv.ipv4, - cv.Required(CONF_SUBNET): cv.ipv4, -}) - -STA_MANUAL_IP_SCHEMA = AP_MANUAL_IP_SCHEMA.extend({ - cv.Optional(CONF_DNS1, default="0.0.0.0"): cv.ipv4, - cv.Optional(CONF_DNS2, default="0.0.0.0"): cv.ipv4, -}) - -EAP_AUTH_SCHEMA = cv.All(cv.Schema({ - cv.Optional(CONF_IDENTITY): cv.string_strict, - cv.Optional(CONF_USERNAME): cv.string_strict, - cv.Optional(CONF_PASSWORD): cv.string_strict, - cv.Optional(CONF_CERTIFICATE_AUTHORITY): wpa2_eap.validate_certificate, - cv.Inclusive(CONF_CERTIFICATE, 'certificate_and_key'): wpa2_eap.validate_certificate, - # Only validate as file first because we need the password to load it - # Actual validation happens in validate_eap. - cv.Inclusive(CONF_KEY, 'certificate_and_key'): cv.file_, -}), wpa2_eap.validate_eap, cv.has_at_least_one_key(CONF_IDENTITY, CONF_CERTIFICATE)) - -WIFI_NETWORK_BASE = cv.Schema({ - cv.GenerateID(): cv.declare_id(WiFiAP), - cv.Optional(CONF_SSID): cv.ssid, - cv.Optional(CONF_PASSWORD): validate_password, - cv.Optional(CONF_CHANNEL): validate_channel, - cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, -}) - -CONF_AP_TIMEOUT = 'ap_timeout' -WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend({ - cv.Optional(CONF_AP_TIMEOUT, default='1min'): cv.positive_time_period_milliseconds, -}) - -WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend({ - cv.Optional(CONF_BSSID): cv.mac_address, - cv.Optional(CONF_HIDDEN): cv.boolean, - cv.Optional(CONF_PRIORITY, default=0.0): cv.float_, - cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA, -}) +AP_MANUAL_IP_SCHEMA = cv.Schema( + { + cv.Required(CONF_STATIC_IP): cv.ipv4, + cv.Required(CONF_GATEWAY): cv.ipv4, + cv.Required(CONF_SUBNET): cv.ipv4, + } +) + +STA_MANUAL_IP_SCHEMA = AP_MANUAL_IP_SCHEMA.extend( + { + cv.Optional(CONF_DNS1, default="0.0.0.0"): cv.ipv4, + cv.Optional(CONF_DNS2, default="0.0.0.0"): cv.ipv4, + } +) + +EAP_AUTH_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_IDENTITY): cv.string_strict, + cv.Optional(CONF_USERNAME): cv.string_strict, + cv.Optional(CONF_PASSWORD): cv.string_strict, + cv.Optional(CONF_CERTIFICATE_AUTHORITY): wpa2_eap.validate_certificate, + cv.Inclusive( + CONF_CERTIFICATE, "certificate_and_key" + ): wpa2_eap.validate_certificate, + # Only validate as file first because we need the password to load it + # Actual validation happens in validate_eap. + cv.Inclusive(CONF_KEY, "certificate_and_key"): cv.file_, + } + ), + wpa2_eap.validate_eap, + cv.has_at_least_one_key(CONF_IDENTITY, CONF_CERTIFICATE), +) + +WIFI_NETWORK_BASE = cv.Schema( + { + cv.GenerateID(): cv.declare_id(WiFiAP), + cv.Optional(CONF_SSID): cv.ssid, + cv.Optional(CONF_PASSWORD): validate_password, + cv.Optional(CONF_CHANNEL): validate_channel, + cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, + } +) + +CONF_AP_TIMEOUT = "ap_timeout" +WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend( + { + cv.Optional( + CONF_AP_TIMEOUT, default="1min" + ): cv.positive_time_period_milliseconds, + } +) + +WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( + { + cv.Optional(CONF_BSSID): cv.mac_address, + cv.Optional(CONF_HIDDEN): cv.boolean, + cv.Optional(CONF_PRIORITY, default=0.0): cv.float_, + cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA, + } +) def validate(config): @@ -105,13 +148,16 @@ def validate(config): if CONF_EAP in config: network[CONF_EAP] = config.pop(CONF_EAP) if CONF_NETWORKS in config: - raise cv.Invalid("You cannot use the 'ssid:' option together with 'networks:'. Please " - "copy your network into the 'networks:' key") + raise cv.Invalid( + "You cannot use the 'ssid:' option together with 'networks:'. Please " + "copy your network into the 'networks:' key" + ) config[CONF_NETWORKS] = cv.ensure_list(WIFI_NETWORK_STA)(network) if (CONF_NETWORKS not in config) and (CONF_AP not in config): - raise cv.Invalid("Please specify at least an SSID or an Access Point " - "to create.") + raise cv.Invalid( + "Please specify at least an SSID or an Access Point " "to create." + ) if config.get(CONF_FAST_CONNECT, False): networks = config.get(CONF_NETWORKS, []) @@ -130,28 +176,36 @@ def validate(config): return config -CONF_OUTPUT_POWER = 'output_power' -CONFIG_SCHEMA = cv.All(cv.Schema({ - cv.GenerateID(): cv.declare_id(WiFiComponent), - cv.Optional(CONF_NETWORKS): cv.ensure_list(WIFI_NETWORK_STA), - - cv.Optional(CONF_SSID): cv.ssid, - cv.Optional(CONF_PASSWORD): validate_password, - cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, - cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA, - - cv.Optional(CONF_AP): WIFI_NETWORK_AP, - cv.Optional(CONF_DOMAIN, default='.local'): cv.domain_name, - cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds, - cv.SplitDefault(CONF_POWER_SAVE_MODE, esp8266='none', esp32='light'): - cv.enum(WIFI_POWER_SAVE_MODES, upper=True), - cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean, - cv.Optional(CONF_USE_ADDRESS): cv.string_strict, - cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All( - cv.decibel, cv.float_range(min=10.0, max=20.5)), - - cv.Optional('hostname'): cv.invalid("The hostname option has been removed in 1.11.0"), -}), validate) +CONF_OUTPUT_POWER = "output_power" +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(WiFiComponent), + cv.Optional(CONF_NETWORKS): cv.ensure_list(WIFI_NETWORK_STA), + cv.Optional(CONF_SSID): cv.ssid, + cv.Optional(CONF_PASSWORD): validate_password, + cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, + cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA, + cv.Optional(CONF_AP): WIFI_NETWORK_AP, + cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, + cv.Optional( + CONF_REBOOT_TIMEOUT, default="15min" + ): cv.positive_time_period_milliseconds, + cv.SplitDefault( + CONF_POWER_SAVE_MODE, esp8266="none", esp32="light" + ): cv.enum(WIFI_POWER_SAVE_MODES, upper=True), + cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean, + cv.Optional(CONF_USE_ADDRESS): cv.string_strict, + cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All( + cv.decibel, cv.float_range(min=10.0, max=20.5) + ), + cv.Optional("hostname"): cv.invalid( + "The hostname option has been removed in 1.11.0" + ), + } + ), + validate, +) def eap_auth(config): @@ -168,12 +222,12 @@ def eap_auth(config): key = wpa2_eap.read_relative_config_path(config[CONF_KEY]) return cg.StructInitializer( EAPAuth, - ('identity', config.get(CONF_IDENTITY, "")), - ('username', config.get(CONF_USERNAME, "")), - ('password', config.get(CONF_PASSWORD, "")), - ('ca_cert', ca_cert), - ('client_cert', client_cert), - ('client_key', key), + ("identity", config.get(CONF_IDENTITY, "")), + ("username", config.get(CONF_USERNAME, "")), + ("password", config.get(CONF_PASSWORD, "")), + ("ca_cert", ca_cert), + ("client_cert", client_cert), + ("client_key", key), ) @@ -188,11 +242,11 @@ def manual_ip(config): return None return cg.StructInitializer( ManualIP, - ('static_ip', safe_ip(config[CONF_STATIC_IP])), - ('gateway', safe_ip(config[CONF_GATEWAY])), - ('subnet', safe_ip(config[CONF_SUBNET])), - ('dns1', safe_ip(config.get(CONF_DNS1))), - ('dns2', safe_ip(config.get(CONF_DNS2))), + ("static_ip", safe_ip(config[CONF_STATIC_IP])), + ("gateway", safe_ip(config[CONF_GATEWAY])), + ("subnet", safe_ip(config[CONF_SUBNET])), + ("dns1", safe_ip(config.get(CONF_DNS1))), + ("dns2", safe_ip(config.get(CONF_DNS2))), ) @@ -204,7 +258,7 @@ def wifi_network(config, static_ip): cg.add(ap.set_password(config[CONF_PASSWORD])) if CONF_EAP in config: cg.add(ap.set_eap(eap_auth(config[CONF_EAP]))) - cg.add_define('ESPHOME_WIFI_WPA2_EAP') + cg.add_define("ESPHOME_WIFI_WPA2_EAP") if CONF_BSSID in config: cg.add(ap.set_bssid([HexInt(i) for i in config[CONF_BSSID].parts])) if CONF_HIDDEN in config: @@ -240,14 +294,14 @@ def to_code(config): cg.add(var.set_output_power(config[CONF_OUTPUT_POWER])) if CORE.is_esp8266: - cg.add_library('ESP8266WiFi', None) + cg.add_library("ESP8266WiFi", None) - cg.add_define('USE_WIFI') + cg.add_define("USE_WIFI") # Register at end for OTA safe mode yield cg.register_component(var, config) -@automation.register_condition('wifi.connected', WiFiConnectedCondition, cv.Schema({})) +@automation.register_condition("wifi.connected", WiFiConnectedCondition, cv.Schema({})) def wifi_connected_to_code(config, condition_id, template_arg, args): yield cg.new_Pvariable(condition_id, template_arg) diff --git a/esphome/components/wifi/wpa2_eap.py b/esphome/components/wifi/wpa2_eap.py index 54195c852b2c..071737ccd700 100644 --- a/esphome/components/wifi/wpa2_eap.py +++ b/esphome/components/wifi/wpa2_eap.py @@ -8,8 +8,13 @@ from esphome.core import CORE import esphome.config_validation as cv -from esphome.const import CONF_USERNAME, CONF_IDENTITY, CONF_PASSWORD, CONF_CERTIFICATE, \ - CONF_KEY +from esphome.const import ( + CONF_USERNAME, + CONF_IDENTITY, + CONF_PASSWORD, + CONF_CERTIFICATE, + CONF_KEY, +) _LOGGER = logging.getLogger(__name__) @@ -19,12 +24,16 @@ def validate_cryptography_installed(): try: import cryptography except ImportError as err: - raise cv.Invalid("This settings requires the cryptography python package. " - "Please install it with `pip install cryptography`") from err - - if cryptography.__version__[0] < '2': - raise cv.Invalid("Please update your python cryptography installation to least 2.x " - "(pip install -U cryptography)") + raise cv.Invalid( + "This settings requires the cryptography python package. " + "Please install it with `pip install cryptography`" + ) from err + + if cryptography.__version__[0] < "2": + raise cv.Invalid( + "Please update your python cryptography installation to least 2.x " + "(pip install -U cryptography)" + ) def wrapped_load_pem_x509_certificate(value): @@ -33,7 +42,7 @@ def wrapped_load_pem_x509_certificate(value): from cryptography import x509 from cryptography.hazmat.backends import default_backend - return x509.load_pem_x509_certificate(value.encode('UTF-8'), default_backend()) + return x509.load_pem_x509_certificate(value.encode("UTF-8"), default_backend()) def wrapped_load_pem_private_key(value, password): @@ -44,7 +53,7 @@ def wrapped_load_pem_private_key(value, password): if password: password = password.encode("UTF-8") - return load_pem_private_key(value.encode('UTF-8'), password, default_backend()) + return load_pem_private_key(value.encode("UTF-8"), password, default_backend()) def read_relative_config_path(value): @@ -72,7 +81,9 @@ def _validate_load_private_key(key, cert_pw): contents = read_relative_config_path(key) return wrapped_load_pem_private_key(contents, cert_pw) except ValueError as e: - raise cv.Invalid(f"There was an error with the EAP 'password:' provided for 'key' {e}") + raise cv.Invalid( + f"There was an error with the EAP 'password:' provided for 'key' {e}" + ) except TypeError as e: raise cv.Invalid(f"There was an error with the EAP 'key:' provided: {e}") @@ -95,10 +106,12 @@ def check_match_b(): # pylint: disable=no-name-in-module from cryptography.hazmat.primitives.asymmetric import ed448, ed25519 - private_key_types.update({ - ed448.Ed448PrivateKey: check_match_b, - ed25519.Ed25519PrivateKey: check_match_b, - }) + private_key_types.update( + { + ed448.Ed448PrivateKey: check_match_b, + ed25519.Ed25519PrivateKey: check_match_b, + } + ) except ImportError: # ed448, ed25519 not supported pass @@ -107,7 +120,7 @@ def check_match_b(): if key_type is None: _LOGGER.warning( "Unrecognised EAP 'certificate:' 'key:' pair format: %s. Proceed with caution!", - type(key) + type(key), ) elif not private_key_types[key_type](): raise cv.Invalid("The provided EAP 'key' is not valid for the 'certificate'.") @@ -120,8 +133,10 @@ def validate_eap(value): value = value.copy() value[CONF_IDENTITY] = value[CONF_USERNAME] if CONF_PASSWORD not in value: - raise cv.Invalid("You cannot use the EAP 'username:' option without a 'password:'. " - "Please provide the 'password:' key") + raise cv.Invalid( + "You cannot use the EAP 'username:' option without a 'password:'. " + "Please provide the 'password:' key" + ) if CONF_CERTIFICATE in value or CONF_KEY in value: # Check the key is valid and for this certificate, just to check the user hasn't pasted diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 56670b417353..07af63524c2a 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -1,31 +1,53 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import CONF_BSSID, CONF_ID, CONF_IP_ADDRESS, CONF_SSID, CONF_MAC_ADDRESS +from esphome.const import ( + CONF_BSSID, + CONF_ID, + CONF_IP_ADDRESS, + CONF_SSID, + CONF_MAC_ADDRESS, +) from esphome.core import coroutine -DEPENDENCIES = ['wifi'] +DEPENDENCIES = ["wifi"] -wifi_info_ns = cg.esphome_ns.namespace('wifi_info') -IPAddressWiFiInfo = wifi_info_ns.class_('IPAddressWiFiInfo', text_sensor.TextSensor, cg.Component) -SSIDWiFiInfo = wifi_info_ns.class_('SSIDWiFiInfo', text_sensor.TextSensor, cg.Component) -BSSIDWiFiInfo = wifi_info_ns.class_('BSSIDWiFiInfo', text_sensor.TextSensor, cg.Component) -MacAddressWifiInfo = wifi_info_ns.class_('MacAddressWifiInfo', text_sensor.TextSensor, cg.Component) +wifi_info_ns = cg.esphome_ns.namespace("wifi_info") +IPAddressWiFiInfo = wifi_info_ns.class_( + "IPAddressWiFiInfo", text_sensor.TextSensor, cg.Component +) +SSIDWiFiInfo = wifi_info_ns.class_("SSIDWiFiInfo", text_sensor.TextSensor, cg.Component) +BSSIDWiFiInfo = wifi_info_ns.class_( + "BSSIDWiFiInfo", text_sensor.TextSensor, cg.Component +) +MacAddressWifiInfo = wifi_info_ns.class_( + "MacAddressWifiInfo", text_sensor.TextSensor, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.Optional(CONF_IP_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(IPAddressWiFiInfo), - }), - cv.Optional(CONF_SSID): text_sensor.TEXT_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(SSIDWiFiInfo), - }), - cv.Optional(CONF_BSSID): text_sensor.TEXT_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(BSSIDWiFiInfo), - }), - cv.Optional(CONF_MAC_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(MacAddressWifiInfo), - }) -}) +CONFIG_SCHEMA = cv.Schema( + { + cv.Optional(CONF_IP_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(IPAddressWiFiInfo), + } + ), + cv.Optional(CONF_SSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SSIDWiFiInfo), + } + ), + cv.Optional(CONF_BSSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BSSIDWiFiInfo), + } + ), + cv.Optional(CONF_MAC_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(MacAddressWifiInfo), + } + ), + } +) @coroutine diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py index 1cc58009af54..f2a9f5408cb9 100644 --- a/esphome/components/wifi_signal/sensor.py +++ b/esphome/components/wifi_signal/sensor.py @@ -1,15 +1,28 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import CONF_ID, ICON_WIFI, UNIT_DECIBEL +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_SIGNAL_STRENGTH, + ICON_EMPTY, + UNIT_DECIBEL, +) -DEPENDENCIES = ['wifi'] -wifi_signal_ns = cg.esphome_ns.namespace('wifi_signal') -WiFiSignalSensor = wifi_signal_ns.class_('WiFiSignalSensor', sensor.Sensor, cg.PollingComponent) +DEPENDENCIES = ["wifi"] +wifi_signal_ns = cg.esphome_ns.namespace("wifi_signal") +WiFiSignalSensor = wifi_signal_ns.class_( + "WiFiSignalSensor", sensor.Sensor, cg.PollingComponent +) -CONFIG_SCHEMA = sensor.sensor_schema(UNIT_DECIBEL, ICON_WIFI, 0).extend({ - cv.GenerateID(): cv.declare_id(WiFiSignalSensor), -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = ( + sensor.sensor_schema(UNIT_DECIBEL, ICON_EMPTY, 0, DEVICE_CLASS_SIGNAL_STRENGTH) + .extend( + { + cv.GenerateID(): cv.declare_id(WiFiSignalSensor), + } + ) + .extend(cv.polling_component_schema("60s")) +) def to_code(config): diff --git a/esphome/components/wled/__init__.py b/esphome/components/wled/__init__.py index 1a248e530f70..31ec3182819a 100644 --- a/esphome/components/wled/__init__.py +++ b/esphome/components/wled/__init__.py @@ -4,15 +4,20 @@ from esphome.components.light.effects import register_addressable_effect from esphome.const import CONF_NAME, CONF_PORT -wled_ns = cg.esphome_ns.namespace('wled') -WLEDLightEffect = wled_ns.class_('WLEDLightEffect', AddressableLightEffect) +wled_ns = cg.esphome_ns.namespace("wled") +WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect) CONFIG_SCHEMA = cv.Schema({}) -@register_addressable_effect('wled', WLEDLightEffect, "WLED", { - cv.Optional(CONF_PORT, default=21324): cv.port, -}) +@register_addressable_effect( + "wled", + WLEDLightEffect, + "WLED", + { + cv.Optional(CONF_PORT, default=21324): cv.port, + }, +) def wled_light_effect_to_code(config, effect_id): effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) cg.add(effect.set_port(config[CONF_PORT])) diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index e06a23aacfe2..3c26beeed485 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -40,11 +40,11 @@ void WLEDLightEffect::stop() { void WLEDLightEffect::blank_all_leds_(light::AddressableLight &it) { for (int led = it.size(); led-- > 0;) { - it[led].set(light::ESPColor::BLACK); + it[led].set(COLOR_BLACK); } } -void WLEDLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) { +void WLEDLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { // Init UDP lazily if (!udp_) { udp_.reset(new WiFiUDP()); @@ -152,7 +152,7 @@ bool WLEDLightEffect::parse_warls_frame_(light::AddressableLight &it, const uint uint8_t b = payload[3]; if (led < max_leds) { - it[led].set(light::ESPColor(r, g, b)); + it[led].set(Color(r, g, b)); } } @@ -174,7 +174,7 @@ bool WLEDLightEffect::parse_drgb_frame_(light::AddressableLight &it, const uint8 uint8_t b = payload[2]; if (led < max_leds) { - it[led].set(light::ESPColor(r, g, b)); + it[led].set(Color(r, g, b)); } } @@ -197,7 +197,7 @@ bool WLEDLightEffect::parse_drgbw_frame_(light::AddressableLight &it, const uint uint8_t w = payload[3]; if (led < max_leds) { - it[led].set(light::ESPColor(r, g, b, w)); + it[led].set(Color(r, g, b, w)); } } @@ -228,7 +228,7 @@ bool WLEDLightEffect::parse_dnrgb_frame_(light::AddressableLight &it, const uint uint8_t b = payload[2]; if (led < max_leds) { - it[led].set(light::ESPColor(r, g, b)); + it[led].set(Color(r, g, b)); } } diff --git a/esphome/components/wled/wled_light_effect.h b/esphome/components/wled/wled_light_effect.h index f1d27b06c770..2a7654ec27a4 100644 --- a/esphome/components/wled/wled_light_effect.h +++ b/esphome/components/wled/wled_light_effect.h @@ -18,7 +18,7 @@ class WLEDLightEffect : public light::AddressableLightEffect { public: void start() override; void stop() override; - void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override; + void apply(light::AddressableLight &it, const Color ¤t_color) override; void set_port(uint16_t port) { this->port_ = port; } protected: diff --git a/esphome/components/xiaomi_ble/__init__.py b/esphome/components/xiaomi_ble/__init__.py index 2b360902936c..3d11ea81258b 100644 --- a/esphome/components/xiaomi_ble/__init__.py +++ b/esphome/components/xiaomi_ble/__init__.py @@ -3,14 +3,18 @@ from esphome.components import esp32_ble_tracker from esphome.const import CONF_ID -DEPENDENCIES = ['esp32_ble_tracker'] +DEPENDENCIES = ["esp32_ble_tracker"] -xiaomi_ble_ns = cg.esphome_ns.namespace('xiaomi_ble') -XiaomiListener = xiaomi_ble_ns.class_('XiaomiListener', esp32_ble_tracker.ESPBTDeviceListener) +xiaomi_ble_ns = cg.esphome_ns.namespace("xiaomi_ble") +XiaomiListener = xiaomi_ble_ns.class_( + "XiaomiListener", esp32_ble_tracker.ESPBTDeviceListener +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiListener), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiListener), + } +).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) def to_code(config): diff --git a/esphome/components/xiaomi_cgd1/sensor.py b/esphome/components/xiaomi_cgd1/sensor.py index 401f6de7d250..25d1f93674cd 100644 --- a/esphome/components/xiaomi_cgd1/sensor.py +++ b/esphome/components/xiaomi_cgd1/sensor.py @@ -1,25 +1,49 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \ - CONF_BINDKEY +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_HUMIDITY, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + CONF_ID, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, + CONF_BINDKEY, +) -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_cgd1_ns = cg.esphome_ns.namespace('xiaomi_cgd1') -XiaomiCGD1 = xiaomi_cgd1_ns.class_('XiaomiCGD1', esp32_ble_tracker.ESPBTDeviceListener, - cg.Component) +xiaomi_cgd1_ns = cg.esphome_ns.namespace("xiaomi_cgd1") +XiaomiCGD1 = xiaomi_cgd1_ns.class_( + "XiaomiCGD1", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiCGD1), - cv.Required(CONF_BINDKEY): cv.bind_key, - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiCGD1), + cv.Required(CONF_BINDKEY): cv.bind_key, + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_cgg1/sensor.py b/esphome/components/xiaomi_cgg1/sensor.py index 897687c68a5c..6201df61b82b 100644 --- a/esphome/components/xiaomi_cgg1/sensor.py +++ b/esphome/components/xiaomi_cgg1/sensor.py @@ -1,23 +1,47 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_HUMIDITY, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + CONF_ID, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, +) -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_cgg1_ns = cg.esphome_ns.namespace('xiaomi_cgg1') +xiaomi_cgg1_ns = cg.esphome_ns.namespace("xiaomi_cgg1") XiaomiCGG1 = xiaomi_cgg1_ns.class_( - 'XiaomiCGG1', esp32_ble_tracker.ESPBTDeviceListener, cg.Component) + "XiaomiCGG1", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiCGG1), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiCGG1), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_gcls002/sensor.py b/esphome/components/xiaomi_gcls002/sensor.py index 1822977c38ef..b8383711555c 100644 --- a/esphome/components/xiaomi_gcls002/sensor.py +++ b/esphome/components/xiaomi_gcls002/sensor.py @@ -1,27 +1,55 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, CONF_ID, \ - CONF_MOISTURE, CONF_ILLUMINANCE, ICON_BRIGHTNESS_5, UNIT_LUX, CONF_CONDUCTIVITY, \ - UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + ICON_WATER_PERCENT, + UNIT_CELSIUS, + UNIT_PERCENT, + CONF_ID, + CONF_MOISTURE, + CONF_ILLUMINANCE, + UNIT_LUX, + CONF_CONDUCTIVITY, + UNIT_MICROSIEMENS_PER_CENTIMETER, + ICON_FLOWER, +) -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_gcls002_ns = cg.esphome_ns.namespace('xiaomi_gcls002') -XiaomiGCLS002 = xiaomi_gcls002_ns.class_('XiaomiGCLS002', - esp32_ble_tracker.ESPBTDeviceListener, cg.Component) +xiaomi_gcls002_ns = cg.esphome_ns.namespace("xiaomi_gcls002") +XiaomiGCLS002 = xiaomi_gcls002_ns.class_( + "XiaomiGCLS002", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiGCLS002), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_MOISTURE): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), - cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 0), - cv.Optional(CONF_CONDUCTIVITY): - sensor.sensor_schema(UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiGCLS002), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_MOISTURE): sensor.sensor_schema( + UNIT_PERCENT, ICON_WATER_PERCENT, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + UNIT_LUX, ICON_EMPTY, 0, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_CONDUCTIVITY): sensor.sensor_schema( + UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER, 0, DEVICE_CLASS_EMPTY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_hhccjcy01/sensor.py b/esphome/components/xiaomi_hhccjcy01/sensor.py index 5e904c7eb614..f657ec937383 100644 --- a/esphome/components/xiaomi_hhccjcy01/sensor.py +++ b/esphome/components/xiaomi_hhccjcy01/sensor.py @@ -1,28 +1,60 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_BATTERY_LEVEL, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \ - CONF_MOISTURE, CONF_ILLUMINANCE, ICON_BRIGHTNESS_5, UNIT_LUX, CONF_CONDUCTIVITY, \ - UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + ICON_WATER_PERCENT, + UNIT_CELSIUS, + UNIT_PERCENT, + CONF_ID, + CONF_MOISTURE, + CONF_ILLUMINANCE, + UNIT_LUX, + CONF_CONDUCTIVITY, + UNIT_MICROSIEMENS_PER_CENTIMETER, + ICON_FLOWER, + DEVICE_CLASS_BATTERY, + CONF_BATTERY_LEVEL, +) -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_hhccjcy01_ns = cg.esphome_ns.namespace('xiaomi_hhccjcy01') -XiaomiHHCCJCY01 = xiaomi_hhccjcy01_ns.class_('XiaomiHHCCJCY01', - esp32_ble_tracker.ESPBTDeviceListener, cg.Component) +xiaomi_hhccjcy01_ns = cg.esphome_ns.namespace("xiaomi_hhccjcy01") +XiaomiHHCCJCY01 = xiaomi_hhccjcy01_ns.class_( + "XiaomiHHCCJCY01", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiHHCCJCY01), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_MOISTURE): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), - cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 0), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), - cv.Optional(CONF_CONDUCTIVITY): - sensor.sensor_schema(UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiHHCCJCY01), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_MOISTURE): sensor.sensor_schema( + UNIT_PERCENT, ICON_WATER_PERCENT, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + UNIT_LUX, ICON_EMPTY, 0, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_CONDUCTIVITY): sensor.sensor_schema( + UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_hhccpot002/sensor.py b/esphome/components/xiaomi_hhccpot002/sensor.py index 33cd96252eb3..820cda173d23 100644 --- a/esphome/components/xiaomi_hhccpot002/sensor.py +++ b/esphome/components/xiaomi_hhccpot002/sensor.py @@ -1,23 +1,42 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_MAC_ADDRESS, UNIT_PERCENT, ICON_WATER_PERCENT, CONF_ID, \ - CONF_MOISTURE, CONF_CONDUCTIVITY, UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER +from esphome.const import ( + CONF_MAC_ADDRESS, + DEVICE_CLASS_EMPTY, + UNIT_PERCENT, + ICON_WATER_PERCENT, + CONF_ID, + CONF_MOISTURE, + CONF_CONDUCTIVITY, + UNIT_MICROSIEMENS_PER_CENTIMETER, + ICON_FLOWER, +) -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_hhccpot002_ns = cg.esphome_ns.namespace('xiaomi_hhccpot002') -XiaomiHHCCPOT002 = xiaomi_hhccpot002_ns.class_('XiaomiHHCCPOT002', - esp32_ble_tracker.ESPBTDeviceListener, cg.Component) +xiaomi_hhccpot002_ns = cg.esphome_ns.namespace("xiaomi_hhccpot002") +XiaomiHHCCPOT002 = xiaomi_hhccpot002_ns.class_( + "XiaomiHHCCPOT002", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiHHCCPOT002), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_MOISTURE): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), - cv.Optional(CONF_CONDUCTIVITY): - sensor.sensor_schema(UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiHHCCPOT002), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_MOISTURE): sensor.sensor_schema( + UNIT_PERCENT, ICON_WATER_PERCENT, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_CONDUCTIVITY): sensor.sensor_schema( + UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER, 0, DEVICE_CLASS_EMPTY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_jqjcy01ym/sensor.py b/esphome/components/xiaomi_jqjcy01ym/sensor.py index 2bd397e82997..ce5e8e2b3700 100644 --- a/esphome/components/xiaomi_jqjcy01ym/sensor.py +++ b/esphome/components/xiaomi_jqjcy01ym/sensor.py @@ -1,26 +1,57 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_BATTERY_LEVEL, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \ - CONF_HUMIDITY, UNIT_MILLIGRAMS_PER_CUBIC_METER, ICON_FLASK_OUTLINE, CONF_FORMALDEHYDE +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + CONF_ID, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_CELSIUS, + UNIT_PERCENT, + CONF_HUMIDITY, + UNIT_MILLIGRAMS_PER_CUBIC_METER, + ICON_FLASK_OUTLINE, + CONF_FORMALDEHYDE, +) -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_jqjcy01ym_ns = cg.esphome_ns.namespace('xiaomi_jqjcy01ym') -XiaomiJQJCY01YM = xiaomi_jqjcy01ym_ns.class_('XiaomiJQJCY01YM', - esp32_ble_tracker.ESPBTDeviceListener, cg.Component) +xiaomi_jqjcy01ym_ns = cg.esphome_ns.namespace("xiaomi_jqjcy01ym") +XiaomiJQJCY01YM = xiaomi_jqjcy01ym_ns.class_( + "XiaomiJQJCY01YM", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiJQJCY01YM), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), - cv.Optional(CONF_FORMALDEHYDE): - sensor.sensor_schema(UNIT_MILLIGRAMS_PER_CUBIC_METER, ICON_FLASK_OUTLINE, 2), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiJQJCY01YM), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_FORMALDEHYDE): sensor.sensor_schema( + UNIT_MILLIGRAMS_PER_CUBIC_METER, + ICON_FLASK_OUTLINE, + 2, + DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_lywsd02/sensor.py b/esphome/components/xiaomi_lywsd02/sensor.py index 97eff0cf79d5..c17eb17a5f07 100644 --- a/esphome/components/xiaomi_lywsd02/sensor.py +++ b/esphome/components/xiaomi_lywsd02/sensor.py @@ -1,23 +1,47 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_HUMIDITY, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + UNIT_CELSIUS, + ICON_EMPTY, + UNIT_PERCENT, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_BATTERY, + CONF_ID, +) -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_lywsd02_ns = cg.esphome_ns.namespace('xiaomi_lywsd02') -XiaomiLYWSD02 = xiaomi_lywsd02_ns.class_('XiaomiLYWSD02', esp32_ble_tracker.ESPBTDeviceListener, - cg.Component) +xiaomi_lywsd02_ns = cg.esphome_ns.namespace("xiaomi_lywsd02") +XiaomiLYWSD02 = xiaomi_lywsd02_ns.class_( + "XiaomiLYWSD02", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiLYWSD02), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiLYWSD02), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_lywsd03mmc/sensor.py b/esphome/components/xiaomi_lywsd03mmc/sensor.py index a1983825fe28..b9de3f0bccd9 100644 --- a/esphome/components/xiaomi_lywsd03mmc/sensor.py +++ b/esphome/components/xiaomi_lywsd03mmc/sensor.py @@ -1,28 +1,51 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \ - CONF_BINDKEY - -CODEOWNERS = ['@ahpohl'] - -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] - -xiaomi_lywsd03mmc_ns = cg.esphome_ns.namespace('xiaomi_lywsd03mmc') -XiaomiLYWSD03MMC = xiaomi_lywsd03mmc_ns.class_('XiaomiLYWSD03MMC', - esp32_ble_tracker.ESPBTDeviceListener, - cg.Component) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiLYWSD03MMC), - cv.Required(CONF_BINDKEY): cv.bind_key, - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_HUMIDITY, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + UNIT_CELSIUS, + ICON_EMPTY, + UNIT_PERCENT, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + CONF_ID, + CONF_BINDKEY, + DEVICE_CLASS_BATTERY, +) + +CODEOWNERS = ["@ahpohl"] + +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] + +xiaomi_lywsd03mmc_ns = cg.esphome_ns.namespace("xiaomi_lywsd03mmc") +XiaomiLYWSD03MMC = xiaomi_lywsd03mmc_ns.class_( + "XiaomiLYWSD03MMC", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiLYWSD03MMC), + cv.Required(CONF_BINDKEY): cv.bind_key, + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_lywsdcgq/sensor.py b/esphome/components/xiaomi_lywsdcgq/sensor.py index e13c8604643c..a4a03a3fb00a 100644 --- a/esphome/components/xiaomi_lywsdcgq/sensor.py +++ b/esphome/components/xiaomi_lywsdcgq/sensor.py @@ -1,23 +1,47 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_HUMIDITY, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + UNIT_CELSIUS, + ICON_EMPTY, + UNIT_PERCENT, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_BATTERY, + CONF_ID, +) -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_lywsdcgq_ns = cg.esphome_ns.namespace('xiaomi_lywsdcgq') -XiaomiLYWSDCGQ = xiaomi_lywsdcgq_ns.class_('XiaomiLYWSDCGQ', esp32_ble_tracker.ESPBTDeviceListener, - cg.Component) +xiaomi_lywsdcgq_ns = cg.esphome_ns.namespace("xiaomi_lywsdcgq") +XiaomiLYWSDCGQ = xiaomi_lywsdcgq_ns.class_( + "XiaomiLYWSDCGQ", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiLYWSDCGQ), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiLYWSDCGQ), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_mhoc401/sensor.py b/esphome/components/xiaomi_mhoc401/sensor.py index 579ca0619af4..ee0e06b3a87f 100644 --- a/esphome/components/xiaomi_mhoc401/sensor.py +++ b/esphome/components/xiaomi_mhoc401/sensor.py @@ -1,27 +1,50 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_BATTERY_LEVEL, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \ - CONF_BINDKEY +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_HUMIDITY, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + UNIT_CELSIUS, + ICON_EMPTY, + UNIT_PERCENT, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + CONF_ID, + CONF_BINDKEY, + DEVICE_CLASS_BATTERY, +) -CODEOWNERS = ['@vevsvevs'] -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +CODEOWNERS = ["@vevsvevs"] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_mhoc401_ns = cg.esphome_ns.namespace('xiaomi_mhoc401') -XiaomiMHOC401 = xiaomi_mhoc401_ns.class_('XiaomiMHOC401', - esp32_ble_tracker.ESPBTDeviceListener, - cg.Component) +xiaomi_mhoc401_ns = cg.esphome_ns.namespace("xiaomi_mhoc401") +XiaomiMHOC401 = xiaomi_mhoc401_ns.class_( + "XiaomiMHOC401", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(XiaomiMHOC401), - cv.Required(CONF_BINDKEY): cv.bind_key, - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiMHOC401), + cv.Required(CONF_BINDKEY): cv.bind_key, + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_miscale/__init__.py b/esphome/components/xiaomi_miscale/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/xiaomi_miscale/sensor.py b/esphome/components/xiaomi_miscale/sensor.py new file mode 100644 index 000000000000..cd225e485315 --- /dev/null +++ b/esphome/components/xiaomi_miscale/sensor.py @@ -0,0 +1,44 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_ID, + CONF_WEIGHT, + UNIT_KILOGRAM, + ICON_SCALE_BATHROOM, + DEVICE_CLASS_EMPTY, +) + +DEPENDENCIES = ["esp32_ble_tracker"] + +xiaomi_miscale_ns = cg.esphome_ns.namespace("xiaomi_miscale") +XiaomiMiscale = xiaomi_miscale_ns.class_( + "XiaomiMiscale", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiMiscale), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_WEIGHT): sensor.sensor_schema( + UNIT_KILOGRAM, ICON_SCALE_BATHROOM, 2, DEVICE_CLASS_EMPTY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield esp32_ble_tracker.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + + if CONF_WEIGHT in config: + sens = yield sensor.new_sensor(config[CONF_WEIGHT]) + cg.add(var.set_weight(sens)) diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp new file mode 100644 index 000000000000..441bca627075 --- /dev/null +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -0,0 +1,101 @@ +#include "xiaomi_miscale.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace xiaomi_miscale { + +static const char *TAG = "xiaomi_miscale"; + +void XiaomiMiscale::dump_config() { + ESP_LOGCONFIG(TAG, "Xiaomi Miscale"); + LOG_SENSOR(" ", "Weight", this->weight_); +} + +bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) { + ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); + return false; + } + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + + bool success = false; + for (auto &service_data : device.get_service_datas()) { + auto res = parse_header(service_data); + if (!res.has_value()) { + continue; + } + if (!(parse_message(service_data.data, *res))) { + continue; + } + if (!(report_results(res, device.address_str()))) { + continue; + } + if (res->weight.has_value() && this->weight_ != nullptr) + this->weight_->publish_state(*res->weight); + success = true; + } + + return success; +} + +optional XiaomiMiscale::parse_header(const esp32_ble_tracker::ServiceData &service_data) { + ParseResult result; + if (!service_data.uuid.contains(0x1D, 0x18)) { + ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); + return {}; + } + + return result; +} + +bool XiaomiMiscale::parse_message(const std::vector &message, ParseResult &result) { + // exemple 1d18 a2 6036 e307 07 11 0f1f11 + // 1-2 Weight (MISCALE 181D) + // 3-4 Years (MISCALE 181D) + // 5 month (MISCALE 181D) + // 6 day (MISCALE 181D) + // 7 hour (MISCALE 181D) + // 8 minute (MISCALE 181D) + // 9 second (MISCALE 181D) + + const uint8_t *data = message.data(); + const int data_length = 10; + + if (message.size() != data_length) { + ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size()); + return false; + } + + // weight, 2 bytes, 16-bit unsigned integer, 1 kg + const int16_t weight = uint16_t(data[1]) | (uint16_t(data[2]) << 8); + if (data[0] == 0x22 || data[0] == 0xa2) + result.weight = weight * 0.01f / 2.0f; // unit 'kg' + else if (data[0] == 0x12 || data[0] == 0xb2) + result.weight = weight * 0.01f * 0.6; // unit 'jin' + else if (data[0] == 0x03 || data[0] == 0xb3) + result.weight = weight * 0.01f * 0.453592; // unit 'lbs' + + return true; +} + +bool XiaomiMiscale::report_results(const optional &result, const std::string &address) { + if (!result.has_value()) { + ESP_LOGVV(TAG, "report_results(): no results available."); + return false; + } + + ESP_LOGD(TAG, "Got Xiaomi Miscale (%s):", address.c_str()); + + if (result->weight.has_value()) { + ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight); + } + + return true; +} + +} // namespace xiaomi_miscale +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h new file mode 100644 index 000000000000..d9da4f942162 --- /dev/null +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -0,0 +1,37 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace xiaomi_miscale { + +struct ParseResult { + optional weight; +}; + +class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { address_ = address; }; + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_weight(sensor::Sensor *weight) { weight_ = weight; } + + protected: + uint64_t address_; + sensor::Sensor *weight_{nullptr}; + + optional parse_header(const esp32_ble_tracker::ServiceData &service_data); + bool parse_message(const std::vector &message, ParseResult &result); + bool report_results(const optional &result, const std::string &address); +}; + +} // namespace xiaomi_miscale +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_miscale2/__init__.py b/esphome/components/xiaomi_miscale2/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/xiaomi_miscale2/sensor.py b/esphome/components/xiaomi_miscale2/sensor.py new file mode 100644 index 000000000000..fa124e8860f3 --- /dev/null +++ b/esphome/components/xiaomi_miscale2/sensor.py @@ -0,0 +1,53 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_ID, + CONF_WEIGHT, + UNIT_KILOGRAM, + ICON_SCALE_BATHROOM, + UNIT_OHM, + CONF_IMPEDANCE, + ICON_OMEGA, + DEVICE_CLASS_EMPTY, +) + +DEPENDENCIES = ["esp32_ble_tracker"] + +xiaomi_miscale2_ns = cg.esphome_ns.namespace("xiaomi_miscale2") +XiaomiMiscale2 = xiaomi_miscale2_ns.class_( + "XiaomiMiscale2", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiMiscale2), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_WEIGHT): sensor.sensor_schema( + UNIT_KILOGRAM, ICON_SCALE_BATHROOM, 2, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_IMPEDANCE): sensor.sensor_schema( + UNIT_OHM, ICON_OMEGA, 0, DEVICE_CLASS_EMPTY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield esp32_ble_tracker.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + + if CONF_WEIGHT in config: + sens = yield sensor.new_sensor(config[CONF_WEIGHT]) + cg.add(var.set_weight(sens)) + if CONF_IMPEDANCE in config: + sens = yield sensor.new_sensor(config[CONF_IMPEDANCE]) + cg.add(var.set_impedance(sens)) diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp new file mode 100644 index 000000000000..2bc4656cb9ba --- /dev/null +++ b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp @@ -0,0 +1,116 @@ +#include "xiaomi_miscale2.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace xiaomi_miscale2 { + +static const char *TAG = "xiaomi_miscale2"; + +void XiaomiMiscale2::dump_config() { + ESP_LOGCONFIG(TAG, "Xiaomi Miscale2"); + LOG_SENSOR(" ", "Weight", this->weight_); + LOG_SENSOR(" ", "Impedance", this->impedance_); +} + +bool XiaomiMiscale2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) { + ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); + return false; + } + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + + bool success = false; + for (auto &service_data : device.get_service_datas()) { + auto res = parse_header(service_data); + if (!res.has_value()) { + continue; + } + if (!(parse_message(service_data.data, *res))) { + continue; + } + if (!(report_results(res, device.address_str()))) { + continue; + } + if (res->weight.has_value() && this->weight_ != nullptr) + this->weight_->publish_state(*res->weight); + if (res->impedance.has_value() && this->impedance_ != nullptr) + this->impedance_->publish_state(*res->impedance); + success = true; + } + + return success; +} + +optional XiaomiMiscale2::parse_header(const esp32_ble_tracker::ServiceData &service_data) { + ParseResult result; + if (!service_data.uuid.contains(0x1B, 0x18)) { + ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes."); + return {}; + } + + return result; +} + +bool XiaomiMiscale2::parse_message(const std::vector &message, ParseResult &result) { + // 2-3 Years (MISCALE 2 181B) + // 4 month (MISCALE 2 181B) + // 5 day (MISCALE 2 181B) + // 6 hour (MISCALE 2 181B) + // 7 minute (MISCALE 2 181B) + // 8 second (MISCALE 2 181B) + // 9-10 impedance (MISCALE 2 181B) + // 11-12 weight (MISCALE 2 181B) + + const uint8_t *data = message.data(); + const int data_length = 13; + + if (message.size() != data_length) { + ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size()); + return false; + } + + bool is_Stabilized = ((data[1] & (1 << 5)) != 0) ? true : false; + bool loadRemoved = ((data[1] & (1 << 7)) != 0) ? true : false; + + // weight, 2 bytes, 16-bit unsigned integer, 1 kg + const int16_t weight = uint16_t(data[11]) | (uint16_t(data[12]) << 8); + if (data[0] == 0x02) + result.weight = weight * 0.01f / 2.0f; // unit 'kg' + else if (data[0] == 0x03) + result.weight = weight * 0.01f * 0.453592; // unit 'lbs' + + // impedance, 2 bytes, 16-bit + const int16_t impedance = uint16_t(data[9]) | (uint16_t(data[10]) << 8); + result.impedance = impedance; + + if (!is_Stabilized || loadRemoved || impedance == 0 || impedance >= 3000) { + return false; + } + + return true; +} + +bool XiaomiMiscale2::report_results(const optional &result, const std::string &address) { + if (!result.has_value()) { + ESP_LOGVV(TAG, "report_results(): no results available."); + return false; + } + + ESP_LOGD(TAG, "Got Xiaomi Miscale2 (%s):", address.c_str()); + + if (result->weight.has_value()) { + ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight); + } + if (result->impedance.has_value()) { + ESP_LOGD(TAG, " Impedance: %.0fohm", *result->impedance); + } + + return true; +} + +} // namespace xiaomi_miscale2 +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h new file mode 100644 index 000000000000..ead522e1f218 --- /dev/null +++ b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace xiaomi_miscale2 { + +struct ParseResult { + optional weight; + optional impedance; +}; + +class XiaomiMiscale2 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { address_ = address; }; + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_weight(sensor::Sensor *weight) { weight_ = weight; } + void set_impedance(sensor::Sensor *impedance) { impedance_ = impedance; } + + protected: + uint64_t address_; + sensor::Sensor *weight_{nullptr}; + sensor::Sensor *impedance_{nullptr}; + + optional parse_header(const esp32_ble_tracker::ServiceData &service_data); + bool parse_message(const std::vector &message, ParseResult &result); + bool report_results(const optional &result, const std::string &address); +}; + +} // namespace xiaomi_miscale2 +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py index e13dd77d13a2..1b0ad03f1a1b 100644 --- a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py +++ b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py @@ -1,29 +1,66 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, binary_sensor, esp32_ble_tracker -from esphome.const import CONF_MAC_ADDRESS, CONF_ID, CONF_BINDKEY, \ - CONF_DEVICE_CLASS, CONF_LIGHT, CONF_BATTERY_LEVEL, UNIT_PERCENT, ICON_BATTERY, \ - CONF_IDLE_TIME, CONF_ILLUMINANCE, UNIT_MINUTE, UNIT_LUX, ICON_TIMELAPSE, ICON_BRIGHTNESS_5 +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_ID, + CONF_BINDKEY, + CONF_DEVICE_CLASS, + CONF_LIGHT, + CONF_BATTERY_LEVEL, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ILLUMINANCE, + ICON_EMPTY, + UNIT_PERCENT, + CONF_IDLE_TIME, + CONF_ILLUMINANCE, + UNIT_MINUTE, + UNIT_LUX, + ICON_TIMELAPSE, +) -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_mjyd02yla_ns = cg.esphome_ns.namespace('xiaomi_mjyd02yla') -XiaomiMJYD02YLA = xiaomi_mjyd02yla_ns.class_('XiaomiMJYD02YLA', binary_sensor.BinarySensor, - cg.Component, esp32_ble_tracker.ESPBTDeviceListener) +xiaomi_mjyd02yla_ns = cg.esphome_ns.namespace("xiaomi_mjyd02yla") +XiaomiMJYD02YLA = xiaomi_mjyd02yla_ns.class_( + "XiaomiMJYD02YLA", + binary_sensor.BinarySensor, + cg.Component, + esp32_ble_tracker.ESPBTDeviceListener, +) -CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(XiaomiMJYD02YLA), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Required(CONF_BINDKEY): cv.bind_key, - cv.Optional(CONF_DEVICE_CLASS, default='motion'): binary_sensor.device_class, - cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema(UNIT_MINUTE, ICON_TIMELAPSE, 0), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), - cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 0), - cv.Optional(CONF_LIGHT): binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.Optional(CONF_DEVICE_CLASS, default='light'): binary_sensor.device_class, - }), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)) +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(XiaomiMJYD02YLA), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Required(CONF_BINDKEY): cv.bind_key, + cv.Optional( + CONF_DEVICE_CLASS, default="motion" + ): binary_sensor.device_class, + cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema( + UNIT_MINUTE, ICON_TIMELAPSE, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + UNIT_LUX, ICON_EMPTY, 0, DEVICE_CLASS_ILLUMINANCE + ), + cv.Optional(CONF_LIGHT): binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.Optional( + CONF_DEVICE_CLASS, default="light" + ): binary_sensor.device_class, + } + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_mue4094rt/binary_sensor.py b/esphome/components/xiaomi_mue4094rt/binary_sensor.py index 946b1694c44f..353e9aa3a668 100644 --- a/esphome/components/xiaomi_mue4094rt/binary_sensor.py +++ b/esphome/components/xiaomi_mue4094rt/binary_sensor.py @@ -4,19 +4,33 @@ from esphome.const import CONF_MAC_ADDRESS, CONF_DEVICE_CLASS, CONF_TIMEOUT, CONF_ID -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] -xiaomi_mue4094rt_ns = cg.esphome_ns.namespace('xiaomi_mue4094rt') -XiaomiMUE4094RT = xiaomi_mue4094rt_ns.class_('XiaomiMUE4094RT', binary_sensor.BinarySensor, - cg.Component, esp32_ble_tracker.ESPBTDeviceListener) +xiaomi_mue4094rt_ns = cg.esphome_ns.namespace("xiaomi_mue4094rt") +XiaomiMUE4094RT = xiaomi_mue4094rt_ns.class_( + "XiaomiMUE4094RT", + binary_sensor.BinarySensor, + cg.Component, + esp32_ble_tracker.ESPBTDeviceListener, +) -CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(XiaomiMUE4094RT), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_DEVICE_CLASS, default='motion'): binary_sensor.device_class, - cv.Optional(CONF_TIMEOUT, default='5s'): cv.positive_time_period_milliseconds, -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)) +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(XiaomiMUE4094RT), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional( + CONF_DEVICE_CLASS, default="motion" + ): binary_sensor.device_class, + cv.Optional( + CONF_TIMEOUT, default="5s" + ): cv.positive_time_period_milliseconds, + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/xiaomi_wx08zm/binary_sensor.py b/esphome/components/xiaomi_wx08zm/binary_sensor.py index 1d60dbf5e02f..c13085b5eb2e 100644 --- a/esphome/components/xiaomi_wx08zm/binary_sensor.py +++ b/esphome/components/xiaomi_wx08zm/binary_sensor.py @@ -1,23 +1,46 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, binary_sensor, esp32_ble_tracker -from esphome.const import CONF_BATTERY_LEVEL, CONF_MAC_ADDRESS, CONF_TABLET, \ - UNIT_PERCENT, ICON_BUG, ICON_BATTERY, CONF_ID - - -DEPENDENCIES = ['esp32_ble_tracker'] -AUTO_LOAD = ['xiaomi_ble'] - -xiaomi_wx08zm_ns = cg.esphome_ns.namespace('xiaomi_wx08zm') -XiaomiWX08ZM = xiaomi_wx08zm_ns.class_('XiaomiWX08ZM', binary_sensor.BinarySensor, - esp32_ble_tracker.ESPBTDeviceListener, cg.Component) - -CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(XiaomiWX08ZM), - cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional(CONF_TABLET): sensor.sensor_schema(UNIT_PERCENT, ICON_BUG, 0), - cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), -}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)) +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_MAC_ADDRESS, + CONF_TABLET, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_EMPTY, + ICON_EMPTY, + UNIT_PERCENT, + ICON_BUG, + CONF_ID, +) + + +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] + +xiaomi_wx08zm_ns = cg.esphome_ns.namespace("xiaomi_wx08zm") +XiaomiWX08ZM = xiaomi_wx08zm_ns.class_( + "XiaomiWX08ZM", + binary_sensor.BinarySensor, + esp32_ble_tracker.ESPBTDeviceListener, + cg.Component, +) + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(XiaomiWX08ZM), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TABLET): sensor.sensor_schema( + UNIT_PERCENT, ICON_BUG, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/yashima/climate.py b/esphome/components/yashima/climate.py index 4c4b98d9e7ec..2965d4cdb8c3 100644 --- a/esphome/components/yashima/climate.py +++ b/esphome/components/yashima/climate.py @@ -4,18 +4,24 @@ from esphome.components.remote_base import CONF_TRANSMITTER_ID from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT -AUTO_LOAD = ['sensor'] +AUTO_LOAD = ["sensor"] -yashima_ns = cg.esphome_ns.namespace('yashima') -YashimaClimate = yashima_ns.class_('YashimaClimate', climate.Climate, cg.Component) +yashima_ns = cg.esphome_ns.namespace("yashima") +YashimaClimate = yashima_ns.class_("YashimaClimate", climate.Climate, cg.Component) -CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(YashimaClimate), - cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent), - cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, - cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, - cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), -}).extend(cv.COMPONENT_SCHEMA)) +CONFIG_SCHEMA = cv.All( + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(YashimaClimate), + cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id( + remote_transmitter.RemoteTransmitterComponent + ), + cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, + cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, + cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), + } + ).extend(cv.COMPONENT_SCHEMA) +) def to_code(config): diff --git a/esphome/components/zyaura/sensor.py b/esphome/components/zyaura/sensor.py index df263974e854..e9035ce10644 100644 --- a/esphome/components/zyaura/sensor.py +++ b/esphome/components/zyaura/sensor.py @@ -2,25 +2,47 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor -from esphome.const import CONF_ID, CONF_CLOCK_PIN, CONF_DATA_PIN, \ - CONF_CO2, CONF_TEMPERATURE, CONF_HUMIDITY, \ - UNIT_PARTS_PER_MILLION, UNIT_CELSIUS, UNIT_PERCENT, \ - ICON_MOLECULE_CO2, ICON_THERMOMETER, ICON_WATER_PERCENT +from esphome.const import ( + CONF_ID, + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_CO2, + CONF_TEMPERATURE, + CONF_HUMIDITY, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + ICON_EMPTY, + UNIT_PARTS_PER_MILLION, + UNIT_CELSIUS, + UNIT_PERCENT, + ICON_MOLECULE_CO2, +) from esphome.cpp_helpers import gpio_pin_expression -zyaura_ns = cg.esphome_ns.namespace('zyaura') -ZyAuraSensor = zyaura_ns.class_('ZyAuraSensor', cg.PollingComponent) +zyaura_ns = cg.esphome_ns.namespace("zyaura") +ZyAuraSensor = zyaura_ns.class_("ZyAuraSensor", cg.PollingComponent) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(ZyAuraSensor), - cv.Required(CONF_CLOCK_PIN): cv.All(pins.internal_gpio_input_pin_schema, - pins.validate_has_interrupt), - cv.Required(CONF_DATA_PIN): cv.All(pins.internal_gpio_input_pin_schema, - pins.validate_has_interrupt), - cv.Optional(CONF_CO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), -}).extend(cv.polling_component_schema('60s')) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(ZyAuraSensor), + cv.Required(CONF_CLOCK_PIN): cv.All( + pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt + ), + cv.Required(CONF_DATA_PIN): cv.All( + pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt + ), + cv.Optional(CONF_CO2): sensor.sensor_schema( + UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY + ), + } +).extend(cv.polling_component_schema("60s")) def to_code(config): diff --git a/esphome/config.py b/esphome/config.py index 048441492914..3317196965f9 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -11,8 +11,13 @@ import voluptuous as vol from esphome import core, core_config, yaml_util -from esphome.const import CONF_ESPHOME, CONF_PLATFORM, ESP_PLATFORMS, CONF_PACKAGES, \ - CONF_SUBSTITUTIONS +from esphome.const import ( + CONF_ESPHOME, + CONF_PLATFORM, + ESP_PLATFORMS, + CONF_PACKAGES, + CONF_SUBSTITUTIONS, +) from esphome.core import CORE, EsphomeError # noqa from esphome.helpers import color, indent from esphome.util import safe_print, OrderedDict @@ -36,39 +41,39 @@ def __init__(self, module, base_components_path, is_core=False, is_platform=Fals @property def is_platform_component(self): - return getattr(self.module, 'IS_PLATFORM_COMPONENT', False) + return getattr(self.module, "IS_PLATFORM_COMPONENT", False) @property def config_schema(self): - return getattr(self.module, 'CONFIG_SCHEMA', None) + return getattr(self.module, "CONFIG_SCHEMA", None) @property def is_multi_conf(self): - return getattr(self.module, 'MULTI_CONF', False) + return getattr(self.module, "MULTI_CONF", False) @property def to_code(self): - return getattr(self.module, 'to_code', None) + return getattr(self.module, "to_code", None) @property def esp_platforms(self): - return getattr(self.module, 'ESP_PLATFORMS', ESP_PLATFORMS) + return getattr(self.module, "ESP_PLATFORMS", ESP_PLATFORMS) @property def dependencies(self): - return getattr(self.module, 'DEPENDENCIES', []) + return getattr(self.module, "DEPENDENCIES", []) @property def conflicts_with(self): - return getattr(self.module, 'CONFLICTS_WITH', []) + return getattr(self.module, "CONFLICTS_WITH", []) @property def auto_load(self): - return getattr(self.module, 'AUTO_LOAD', []) + return getattr(self.module, "AUTO_LOAD", []) @property def codeowners(self) -> List[str]: - return getattr(self.module, 'CODEOWNERS', []) + return getattr(self.module, "CODEOWNERS", []) def _get_flags_set(self, name, config): if not hasattr(self.module, name): @@ -85,11 +90,11 @@ def _get_flags_set(self, name, config): @property def source_files(self): if self._is_core: - core_p = os.path.abspath(os.path.join(os.path.dirname(__file__), 'core')) - source_files = core.find_source_files(os.path.join(core_p, 'dummy')) + core_p = os.path.abspath(os.path.join(os.path.dirname(__file__), "core")) + source_files = core.find_source_files(os.path.join(core_p, "dummy")) ret = {} for f in source_files: - ret[f'esphome/core/{f}'] = os.path.join(core_p, f) + ret[f"esphome/core/{f}"] = os.path.join(core_p, f) return ret source_files = core.find_source_files(self.module.__file__) @@ -100,13 +105,15 @@ def source_files(self): full_file = os.path.join(directory, x) rel = os.path.relpath(full_file, self.base_components_path) # Always use / for C++ include names - rel = rel.replace(os.sep, '/') - target_file = f'esphome/components/{rel}' + rel = rel.replace(os.sep, "/") + target_file = f"esphome/components/{rel}" ret[target_file] = full_file return ret -CORE_COMPONENTS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), 'components')) +CORE_COMPONENTS_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), "components") +) _UNDEF = object() CUSTOM_COMPONENTS_PATH = _UNDEF @@ -115,7 +122,7 @@ def _mount_config_dir(): global CUSTOM_COMPONENTS_PATH if CUSTOM_COMPONENTS_PATH is not _UNDEF: return - custom_path = os.path.abspath(os.path.join(CORE.config_dir, 'custom_components')) + custom_path = os.path.abspath(os.path.join(CORE.config_dir, "custom_components")) if not os.path.isdir(custom_path): CUSTOM_COMPONENTS_PATH = None return @@ -131,25 +138,29 @@ def _lookup_module(domain, is_platform): _mount_config_dir() # First look for custom_components try: - module = importlib.import_module(f'custom_components.{domain}') + module = importlib.import_module(f"custom_components.{domain}") except ImportError as e: # ImportError when no such module - if 'No module named' not in str(e): - _LOGGER.warning("Unable to import custom component %s:", domain, exc_info=True) + if "No module named" not in str(e): + _LOGGER.warning( + "Unable to import custom component %s:", domain, exc_info=True + ) except Exception: # pylint: disable=broad-except # Other error means component has an issue _LOGGER.error("Unable to load custom component %s:", domain, exc_info=True) return None else: # Found in custom components - manif = ComponentManifest(module, CUSTOM_COMPONENTS_PATH, is_platform=is_platform) + manif = ComponentManifest( + module, CUSTOM_COMPONENTS_PATH, is_platform=is_platform + ) _COMPONENT_CACHE[domain] = manif return manif try: - module = importlib.import_module(f'esphome.components.{domain}') + module = importlib.import_module(f"esphome.components.{domain}") except ImportError as e: - if 'No module named' not in str(e): + if "No module named" not in str(e): _LOGGER.error("Unable to import component %s:", domain, exc_info=True) return None except Exception: # pylint: disable=broad-except @@ -162,17 +173,20 @@ def _lookup_module(domain, is_platform): def get_component(domain): - assert '.' not in domain + assert "." not in domain return _lookup_module(domain, False) def get_platform(domain, platform): - full = f'{platform}.{domain}' + full = f"{platform}.{domain}" return _lookup_module(full, True) -_COMPONENT_CACHE['esphome'] = ComponentManifest( - core_config, CORE_COMPONENTS_PATH, is_core=True, is_platform=False, +_COMPONENT_CACHE["esphome"] = ComponentManifest( + core_config, + CORE_COMPONENTS_PATH, + is_core=True, + is_platform=False, ) @@ -197,7 +211,7 @@ def iter_components(config): def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool if len(path) < len(other): return False - return path[:len(other)] == other + return path[: len(other)] == other class Config(OrderedDict): @@ -265,11 +279,22 @@ def get_deepest_document_range_for_path(self, path): doc_range = None for item_index in path: try: + if item_index in data: + doc_range = [x for x in data.keys() if x == item_index][0].esp_range data = data[item_index] - except (KeyError, IndexError, TypeError): + except (KeyError, IndexError, TypeError, AttributeError): return doc_range + if isinstance(data, core.ID): + data = data.id if isinstance(data, ESPHomeDataBase) and data.esp_range is not None: doc_range = data.esp_range + elif isinstance(data, dict): + platform_item = data.get("platform") + if ( + isinstance(platform_item, ESPHomeDataBase) + and platform_item.esp_range is not None + ): + doc_range = platform_item.esp_range return doc_range @@ -324,7 +349,7 @@ def do_id_pass(result): # type: (Config) -> None # Look for duplicate definitions match = next((v for v in declare_ids if v[0].id == id.id), None) if match is not None: - opath = '->'.join(str(v) for v in match[1]) + opath = "->".join(str(v) for v in match[1]) result.add_str_error(f"ID {id.id} redefined! Check {opath}", path) continue declare_ids.append((id, path)) @@ -341,35 +366,67 @@ def do_id_pass(result): # type: (Config) -> None if id.id is not None: # manually declared match = next((v[0] for v in declare_ids if v[0].id == id.id), None) - if match is None: + if match is None or not match.is_manual: # No declared ID with this name import difflib - error = ("Couldn't find ID '{}'. Please check you have defined " - "an ID with that name in your configuration.".format(id.id)) + + error = ( + "Couldn't find ID '{}'. Please check you have defined " + "an ID with that name in your configuration.".format(id.id) + ) # Find candidates - matches = difflib.get_close_matches(id.id, [v[0].id for v in declare_ids]) + matches = difflib.get_close_matches( + id.id, [v[0].id for v in declare_ids if v[0].is_manual] + ) if matches: - matches_s = ', '.join(f'"{x}"' for x in matches) + matches_s = ", ".join(f'"{x}"' for x in matches) error += f" These IDs look similar: {matches_s}." result.add_str_error(error, path) continue - if not isinstance(match.type, MockObjClass) or not isinstance(id.type, MockObjClass): + if not isinstance(match.type, MockObjClass) or not isinstance( + id.type, MockObjClass + ): continue if not match.type.inherits_from(id.type): - result.add_str_error("ID '{}' of type {} doesn't inherit from {}. Please " - "double check your ID is pointing to the correct value" - "".format(id.id, match.type, id.type), path) + result.add_str_error( + "ID '{}' of type {} doesn't inherit from {}. Please " + "double check your ID is pointing to the correct value" + "".format(id.id, match.type, id.type), + path, + ) if id.id is None and id.type is not None: + matches = [] for v in declare_ids: if v[0] is None or not isinstance(v[0].type, MockObjClass): continue inherits = v[0].type.inherits_from(id.type) if inherits: - id.id = v[0].id - break - else: - result.add_str_error(f"Couldn't resolve ID for type '{id.type}'", path) + matches.append(v[0]) + + if len(matches) == 0: + result.add_str_error( + f"Couldn't find any component that can be used for '{id.type}'. Are you missing a hub declaration?", + path, + ) + elif len(matches) == 1: + id.id = matches[0].id + elif len(matches) > 1: + if str(id.type) == "time::RealTimeClock": + id.id = matches[0].id + else: + manual_declared_count = sum(1 for m in matches if m.is_manual) + if manual_declared_count > 0: + ids = ", ".join([f"'{m.id}'" for m in matches if m.is_manual]) + result.add_str_error( + f"Too many candidates found for '{path[-1]}' type '{id.type}' {'Some are' if manual_declared_count > 1 else 'One is'} {ids}", + path, + ) + else: + result.add_str_error( + f"Too many candidates found for '{path[-1]}' type '{id.type}' You must assign an explicit ID to the parent component you want to use.", + path, + ) def recursive_check_replaceme(value): @@ -381,12 +438,14 @@ def recursive_check_replaceme(value): return cv.Schema({cv.valid: recursive_check_replaceme})(value) if isinstance(value, ESPForceValue): pass - if isinstance(value, str) and value == 'REPLACEME': - raise cv.Invalid("Found 'REPLACEME' in configuration, this is most likely an error. " - "Please make sure you have replaced all fields from the sample " - "configuration.\n" - "If you want to use the literal REPLACEME string, " - "please use \"!force REPLACEME\"") + if isinstance(value, str) and value == "REPLACEME": + raise cv.Invalid( + "Found 'REPLACEME' in configuration, this is most likely an error. " + "Please make sure you have replaced all fields from the sample " + "configuration.\n" + "If you want to use the literal REPLACEME string, " + 'please use "!force REPLACEME"' + ) return value @@ -396,6 +455,7 @@ def validate_config(config, command_line_substitutions): # 0. Load packages if CONF_PACKAGES in config: from esphome.components.packages import do_packages_pass + result.add_output_path([CONF_PACKAGES], CONF_PACKAGES) try: config = do_packages_pass(config) @@ -407,7 +467,11 @@ def validate_config(config, command_line_substitutions): # 1. Load substitutions if CONF_SUBSTITUTIONS in config: from esphome.components import substitutions - result[CONF_SUBSTITUTIONS] = {**config[CONF_SUBSTITUTIONS], **command_line_substitutions} + + result[CONF_SUBSTITUTIONS] = { + **config[CONF_SUBSTITUTIONS], + **command_line_substitutions, + } result.add_output_path([CONF_SUBSTITUTIONS], CONF_SUBSTITUTIONS) try: substitutions.do_substitution_pass(config, command_line_substitutions) @@ -421,14 +485,19 @@ def validate_config(config, command_line_substitutions): except vol.Invalid as err: result.add_error(err) - if 'esphomeyaml' in config: - _LOGGER.warning("The esphomeyaml section has been renamed to esphome in 1.11.0. " - "Please replace 'esphomeyaml:' in your configuration with 'esphome:'.") - config[CONF_ESPHOME] = config.pop('esphomeyaml') + if "esphomeyaml" in config: + _LOGGER.warning( + "The esphomeyaml section has been renamed to esphome in 1.11.0. " + "Please replace 'esphomeyaml:' in your configuration with 'esphome:'." + ) + config[CONF_ESPHOME] = config.pop("esphomeyaml") if CONF_ESPHOME not in config: - result.add_str_error("'esphome' section missing from configuration. Please make sure " - "your configuration has an 'esphome:' line in it.", []) + result.add_str_error( + "'esphome' section missing from configuration. Please make sure " + "your configuration has an 'esphome:' line in it.", + [], + ) return result # 2. Load partial core config @@ -450,7 +519,9 @@ def validate_config(config, command_line_substitutions): load_queue.append((domain, conf)) # List of items to enter next stage - check_queue = [] # type: List[Tuple[ConfigPath, str, ConfigType, ComponentManifest]] + check_queue = ( + [] + ) # type: List[Tuple[ConfigPath, str, ConfigType, ComponentManifest]] # This step handles: # - Adding output path @@ -459,8 +530,7 @@ def validate_config(config, command_line_substitutions): while load_queue: domain, conf = load_queue.popleft() - domain = str(domain) - if domain.startswith('.'): + if domain.startswith("."): # Ignore top-level keys starting with a dot continue result.add_output_path([domain], domain) @@ -496,19 +566,19 @@ def validate_config(config, command_line_substitutions): for i, p_config in enumerate(conf): path = [domain, i] # Construct temporary unknown output path - p_domain = f'{domain}.unknown' + p_domain = f"{domain}.unknown" result.add_output_path(path, p_domain) result[domain][i] = p_config if not isinstance(p_config, dict): result.add_str_error("Platform schemas must be key-value pairs.", path) continue - p_name = p_config.get('platform') + p_name = p_config.get("platform") if p_name is None: result.add_str_error("No platform specified! See 'platform' key.", path) continue # Remove temp output path and construct new one result.remove_output_path(path, p_domain) - p_domain = f'{domain}.{p_name}' + p_domain = f"{domain}.{p_name}" result.add_output_path(path, p_domain) # Try Load platform platform = get_platform(domain, p_name) @@ -541,8 +611,10 @@ def validate_config(config, command_line_substitutions): success = True for dependency in comp.dependencies: if dependency not in config: - result.add_str_error("Component {} requires component {}" - "".format(domain, dependency), path) + result.add_str_error( + "Component {} requires component {}" "".format(domain, dependency), + path, + ) success = False if not success: continue @@ -550,22 +622,32 @@ def validate_config(config, command_line_substitutions): success = True for conflict in comp.conflicts_with: if conflict in config: - result.add_str_error("Component {} cannot be used together with component {}" - "".format(domain, conflict), path) + result.add_str_error( + "Component {} cannot be used together with component {}" + "".format(domain, conflict), + path, + ) success = False if not success: continue if CORE.esp_platform not in comp.esp_platforms: - result.add_str_error("Component {} doesn't support {}.".format(domain, - CORE.esp_platform), - path) + result.add_str_error( + "Component {} doesn't support {}.".format(domain, CORE.esp_platform), + path, + ) continue - if not comp.is_platform_component and comp.config_schema is None and \ - not isinstance(conf, core.AutoLoad): - result.add_str_error("Component {} cannot be loaded via YAML " - "(no CONFIG_SCHEMA).".format(domain), path) + if ( + not comp.is_platform_component + and comp.config_schema is None + and not isinstance(conf, core.AutoLoad) + ): + result.add_str_error( + "Component {} cannot be loaded via YAML " + "(no CONFIG_SCHEMA).".format(domain), + path, + ) continue if comp.is_multi_conf: @@ -585,13 +667,13 @@ def validate_config(config, command_line_substitutions): if comp.is_platform: # Remove 'platform' key for validation input_conf = OrderedDict(conf) - platform_val = input_conf.pop('platform') + platform_val = input_conf.pop("platform") validated = comp.config_schema(input_conf) # Ensure result is OrderedDict so we can call move_to_end if not isinstance(validated, OrderedDict): validated = OrderedDict(validated) - validated['platform'] = platform_val - validated.move_to_end('platform', last=False) + validated["platform"] = platform_val + validated.move_to_end("platform", last=False) result.set_by_path(path, validated) else: validated = comp.config_schema(conf) @@ -616,18 +698,20 @@ def _nested_getitem(data, path): def humanize_error(config, validation_error): validation_error = str(validation_error) - m = re.match(r'^(.*?)\s*(?:for dictionary value )?@ data\[.*$', validation_error, re.DOTALL) + m = re.match( + r"^(.*?)\s*(?:for dictionary value )?@ data\[.*$", validation_error, re.DOTALL + ) if m is not None: validation_error = m.group(1) validation_error = validation_error.strip() - if not validation_error.endswith('.'): - validation_error += '.' + if not validation_error.endswith("."): + validation_error += "." return validation_error def _get_parent_name(path, config): if not path: - return '' + return "" for domain_path, domain in config.output_paths: if _path_begins_with(path, domain_path): if len(path) > len(domain_path): @@ -639,20 +723,22 @@ def _get_parent_name(path, config): def _format_vol_invalid(ex, config): # type: (vol.Invalid, Config) -> str - message = '' + message = "" paren = _get_parent_name(ex.path[:-1], config) if isinstance(ex, ExtraKeysInvalid): if ex.candidates: - message += '[{}] is an invalid option for [{}]. Did you mean {}?'.format( - ex.path[-1], paren, ', '.join(f'[{x}]' for x in ex.candidates)) + message += "[{}] is an invalid option for [{}]. Did you mean {}?".format( + ex.path[-1], paren, ", ".join(f"[{x}]" for x in ex.candidates) + ) else: - message += '[{}] is an invalid option for [{}]. Please check the indentation.'.format( - ex.path[-1], paren) - elif 'extra keys not allowed' in str(ex): - message += '[{}] is an invalid option for [{}].'.format(ex.path[-1], paren) - elif 'required key not provided' in str(ex): + message += "[{}] is an invalid option for [{}]. Please check the indentation.".format( + ex.path[-1], paren + ) + elif "extra keys not allowed" in str(ex): + message += "[{}] is an invalid option for [{}].".format(ex.path[-1], paren) + elif "required key not provided" in str(ex): message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) else: message += humanize_error(config, ex) @@ -696,15 +782,16 @@ def load_config(command_line_substitutions): raise EsphomeError(f"Error while parsing config: {err}") from err -def line_info(obj, highlight=True): +def line_info(config, path, highlight=True): """Display line config source.""" if not highlight: return None - if isinstance(obj, ESPHomeDataBase) and obj.esp_range is not None: - mark = obj.esp_range.start_mark + obj = config.get_deepest_document_range_for_path(path) + if obj: + mark = obj.start_mark source = "[source {}:{}]".format(mark.document, mark.line + 1) - return color('cyan', source) - return None + return color("cyan", source) + return "None" def _print_on_next_line(obj): @@ -720,90 +807,94 @@ def _print_on_next_line(obj): def dump_dict(config, path, at_root=True): # type: (Config, ConfigPath, bool) -> Tuple[str, bool] conf = config.get_nested_item(path) - ret = '' + ret = "" multiline = False if at_root: error = config.get_error_for_path(path) if error is not None: - ret += '\n' + color('bold_red', _format_vol_invalid(error, config)) + '\n' + ret += "\n" + color("bold_red", _format_vol_invalid(error, config)) + "\n" if isinstance(conf, (list, tuple)): multiline = True if not conf: - ret += '[]' + ret += "[]" multiline = False for i in range(len(conf)): path_ = path + [i] error = config.get_error_for_path(path_) if error is not None: - ret += '\n' + color('bold_red', _format_vol_invalid(error, config)) + '\n' + ret += ( + "\n" + color("bold_red", _format_vol_invalid(error, config)) + "\n" + ) - sep = '- ' + sep = "- " if config.is_in_error_path(path_): - sep = color('red', sep) + sep = color("red", sep) msg, _ = dump_dict(config, path_, at_root=False) msg = indent(msg) - inf = line_info(config.get_nested_item(path_), highlight=config.is_in_error_path(path_)) + inf = line_info(config, path_, highlight=config.is_in_error_path(path_)) if inf is not None: - msg = inf + '\n' + msg + msg = inf + "\n" + msg elif msg: msg = msg[2:] - ret += sep + msg + '\n' + ret += sep + msg + "\n" elif isinstance(conf, dict): multiline = True if not conf: - ret += '{}' + ret += "{}" multiline = False for k in conf.keys(): path_ = path + [k] error = config.get_error_for_path(path_) if error is not None: - ret += '\n' + color('bold_red', _format_vol_invalid(error, config)) + '\n' + ret += ( + "\n" + color("bold_red", _format_vol_invalid(error, config)) + "\n" + ) - st = f'{k}: ' + st = f"{k}: " if config.is_in_error_path(path_): - st = color('red', st) + st = color("red", st) msg, m = dump_dict(config, path_, at_root=False) - inf = line_info(config.get_nested_item(path_), highlight=config.is_in_error_path(path_)) + inf = line_info(config, path_, highlight=config.is_in_error_path(path_)) if m: - msg = '\n' + indent(msg) + msg = "\n" + indent(msg) if inf is not None: if m: - msg = ' ' + inf + msg + msg = " " + inf + msg else: - msg = msg + ' ' + inf - ret += st + msg + '\n' + msg = msg + " " + inf + ret += st + msg + "\n" elif isinstance(conf, str): if is_secret(conf): - conf = '!secret {}'.format(is_secret(conf)) + conf = "!secret {}".format(is_secret(conf)) if not conf: conf += "''" if len(conf) > 80: - conf = '|-\n' + indent(conf) + conf = "|-\n" + indent(conf) error = config.get_error_for_path(path) - col = 'bold_red' if error else 'white' + col = "bold_red" if error else "white" ret += color(col, str(conf)) elif isinstance(conf, core.Lambda): if is_secret(conf): - conf = '!secret {}'.format(is_secret(conf)) + conf = "!secret {}".format(is_secret(conf)) - conf = '!lambda |-\n' + indent(str(conf.value)) + conf = "!lambda |-\n" + indent(str(conf.value)) error = config.get_error_for_path(path) - col = 'bold_red' if error else 'white' + col = "bold_red" if error else "white" ret += color(col, conf) elif conf is None: pass else: error = config.get_error_for_path(path) - col = 'bold_red' if error else 'white' + col = "bold_red" if error else "white" ret += color(col, str(conf)) - multiline = '\n' in ret + multiline = "\n" in ret return ret, multiline @@ -813,7 +904,9 @@ def strip_default_ids(config): to_remove = [] for i, x in enumerate(config): x = config[i] = strip_default_ids(x) - if (isinstance(x, core.ID) and not x.is_manual) or isinstance(x, core.AutoLoad): + if (isinstance(x, core.ID) and not x.is_manual) or isinstance( + x, core.AutoLoad + ): to_remove.append(x) for x in to_remove: config.remove(x) @@ -821,7 +914,9 @@ def strip_default_ids(config): to_remove = [] for k, v in config.items(): v = config[k] = strip_default_ids(v) - if (isinstance(v, core.ID) and not v.is_manual) or isinstance(v, core.AutoLoad): + if (isinstance(v, core.ID) and not v.is_manual) or isinstance( + v, core.AutoLoad + ): to_remove.append(k) for k in to_remove: config.pop(k) @@ -839,14 +934,17 @@ def read_config(command_line_substitutions): if not CORE.verbose: res = strip_default_ids(res) - safe_print(color('bold_red', "Failed config")) - safe_print('') + safe_print(color("bold_red", "Failed config")) + safe_print("") for path, domain in res.output_paths: if not res.is_in_error_path(path): continue - safe_print(color('bold_red', f'{domain}:') + ' ' + - (line_info(res.get_nested_item(path)) or '')) + errstr = color("bold_red", f"{domain}:") + errline = line_info(res, path) + if errline: + errstr += " " + errline + safe_print(errstr) safe_print(indent(dump_dict(res, path)[0])) return None return OrderedDict(res) diff --git a/esphome/config_helpers.py b/esphome/config_helpers.py index dcbcb70efea6..a88c5983b54f 100644 --- a/esphome/config_helpers.py +++ b/esphome/config_helpers.py @@ -7,14 +7,19 @@ def read_config_file(path): # type: (str) -> str - if CORE.vscode and (not CORE.ace or - os.path.abspath(path) == os.path.abspath(CORE.config_path)): - print(json.dumps({ - 'type': 'read_file', - 'path': path, - })) + if CORE.vscode and ( + not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path) + ): + print( + json.dumps( + { + "type": "read_file", + "path": path, + } + ) + ) data = json.loads(input()) - assert data['type'] == 'file_response' - return data['content'] + assert data["type"] == "file_response" + return data["content"] return read_file(path) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index a5b1559f2f0c..4a65c593798f 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -11,15 +11,44 @@ import voluptuous as vol from esphome import core -from esphome.const import ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TOPIC, \ - CONF_DISCOVERY, CONF_ID, CONF_INTERNAL, CONF_NAME, CONF_PAYLOAD_AVAILABLE, \ - CONF_PAYLOAD_NOT_AVAILABLE, CONF_RETAIN, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_TOPIC, \ - CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, \ - CONF_TYPE, CONF_PACKAGES -from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \ - TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes +from esphome.const import ( + ALLOWED_NAME_CHARS, + CONF_AVAILABILITY, + CONF_COMMAND_TOPIC, + CONF_DISCOVERY, + CONF_ID, + CONF_INTERNAL, + CONF_NAME, + CONF_PAYLOAD_AVAILABLE, + CONF_PAYLOAD_NOT_AVAILABLE, + CONF_RETAIN, + CONF_SETUP_PRIORITY, + CONF_STATE_TOPIC, + CONF_TOPIC, + CONF_HOUR, + CONF_MINUTE, + CONF_SECOND, + CONF_VALUE, + CONF_UPDATE_INTERVAL, + CONF_TYPE_ID, + CONF_TYPE, + CONF_PACKAGES, +) +from esphome.core import ( + CORE, + HexInt, + IPAddress, + Lambda, + TimePeriod, + TimePeriodMicroseconds, + TimePeriodMilliseconds, + TimePeriodSeconds, + TimePeriodMinutes, +) from esphome.helpers import list_starts_with, add_class_to_obj +from esphome.jsonschema import jschema_composite, jschema_registry, jschema_typed from esphome.voluptuous_schema import _Schema +from esphome.yaml_util import make_data_base _LOGGER = logging.getLogger(__name__) @@ -43,21 +72,115 @@ RESERVED_IDS = [ # C++ keywords http://en.cppreference.com/w/cpp/keyword - 'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor', 'bool', 'break', - 'case', 'catch', 'char', 'char16_t', 'char32_t', 'class', 'compl', 'concept', 'const', - 'constexpr', 'const_cast', 'continue', 'decltype', 'default', 'delete', 'do', 'double', - 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'export', 'extern', 'false', 'float', - 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', - 'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq', 'private', 'protected', - 'public', 'register', 'reinterpret_cast', 'requires', 'return', 'short', 'signed', 'sizeof', - 'static', 'static_assert', 'static_cast', 'struct', 'switch', 'template', 'this', - 'thread_local', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', - 'using', 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'xor', 'xor_eq', - - 'App', 'pinMode', 'delay', 'delayMicroseconds', 'digitalRead', 'digitalWrite', 'INPUT', - 'OUTPUT', - 'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t', 'int8_t', 'int16_t', 'int32_t', 'int64_t', - 'close', 'pause', 'sleep', 'open', 'setup', 'loop', + "alignas", + "alignof", + "and", + "and_eq", + "asm", + "auto", + "bitand", + "bitor", + "bool", + "break", + "case", + "catch", + "char", + "char16_t", + "char32_t", + "class", + "compl", + "concept", + "const", + "constexpr", + "const_cast", + "continue", + "decltype", + "default", + "delete", + "do", + "double", + "dynamic_cast", + "else", + "enum", + "explicit", + "export", + "export", + "extern", + "false", + "float", + "for", + "friend", + "goto", + "if", + "inline", + "int", + "long", + "mutable", + "namespace", + "new", + "noexcept", + "not", + "not_eq", + "nullptr", + "operator", + "or", + "or_eq", + "private", + "protected", + "public", + "register", + "reinterpret_cast", + "requires", + "return", + "short", + "signed", + "sizeof", + "static", + "static_assert", + "static_cast", + "struct", + "switch", + "template", + "this", + "thread_local", + "throw", + "true", + "try", + "typedef", + "typeid", + "typename", + "union", + "unsigned", + "using", + "virtual", + "void", + "volatile", + "wchar_t", + "while", + "xor", + "xor_eq", + "App", + "pinMode", + "delay", + "delayMicroseconds", + "digitalRead", + "digitalWrite", + "INPUT", + "OUTPUT", + "uint8_t", + "uint16_t", + "uint32_t", + "uint64_t", + "int8_t", + "int16_t", + "int32_t", + "int64_t", + "close", + "pause", + "sleep", + "open", + "setup", + "loop", ] @@ -111,8 +234,10 @@ def valid_name(value): value = string_strict(value) for c in value: if c not in ALLOWED_NAME_CHARS: - raise Invalid(f"'{c}' is an invalid character for names. Valid characters are: " - f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)") + raise Invalid( + f"'{c}' is an invalid character for names. Valid characters are: " + f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)" + ) return value @@ -126,7 +251,9 @@ def string(value): if isinstance(value, (dict, list)): raise Invalid("string value cannot be dictionary or list.") if isinstance(value, bool): - raise Invalid("Auto-converted this value to boolean, please wrap the value in quotes.") + raise Invalid( + "Auto-converted this value to boolean, please wrap the value in quotes." + ) if isinstance(value, str): return value if value is not None: @@ -140,8 +267,10 @@ def string_strict(value): check_not_templatable(value) if isinstance(value, str): return value - raise Invalid("Must be string, got {}. did you forget putting quotes " - "around the value?".format(type(value))) + raise Invalid( + "Must be string, got {}. did you forget putting quotes " + "around the value?".format(type(value)) + ) def icon(value): @@ -149,7 +278,7 @@ def icon(value): value = string_strict(value) if not value: return value - if value.startswith('mdi:'): + if value.startswith("mdi:"): return value raise Invalid('Icons should start with prefix "mdi:"') @@ -168,14 +297,17 @@ def boolean(value): return value if isinstance(value, str): value = value.lower() - if value in ('true', 'yes', 'on', 'enable'): + if value in ("true", "yes", "on", "enable"): return True - if value in ('false', 'no', 'off', 'disable'): + if value in ("false", "no", "off", "disable"): return False - raise Invalid("Expected boolean value, but cannot convert {} to a boolean. " - "Please use 'true' or 'false'".format(value)) + raise Invalid( + "Expected boolean value, but cannot convert {} to a boolean. " + "Please use 'true' or 'false'".format(value) + ) +@jschema_composite def ensure_list(*validators): """Validate this configuration option to be a list. @@ -216,11 +348,13 @@ def int_(value): if isinstance(value, float): if int(value) == value: return int(value) - raise Invalid("This option only accepts integers with no fractional part. Please remove " - "the fractional part from {}".format(value)) + raise Invalid( + "This option only accepts integers with no fractional part. Please remove " + "the fractional part from {}".format(value) + ) value = string_strict(value).lower() base = 10 - if value.startswith('0x'): + if value.startswith("0x"): base = 16 try: return int(value, base) @@ -235,13 +369,18 @@ def int_range(min=None, max=None, min_included=True, max_included=True): assert isinstance(min, int) if max is not None: assert isinstance(max, int) - return All(int_, Range(min=min, max=max, min_included=min_included, max_included=max_included)) + return All( + int_, + Range(min=min, max=max, min_included=min_included, max_included=max_included), + ) def hex_int_range(min=None, max=None, min_included=True, max_included=True): """Validate that the config option is an integer in the given range.""" - return All(hex_int, - Range(min=min, max=max, min_included=min_included, max_included=max_included)) + return All( + hex_int, + Range(min=min, max=max, min_included=min_included, max_included=max_included), + ) def float_range(min=None, max=None, min_included=True, max_included=True): @@ -250,8 +389,10 @@ def float_range(min=None, max=None, min_included=True, max_included=True): assert isinstance(min, (int, float)) if max is not None: assert isinstance(max, (int, float)) - return All(float_, Range(min=min, max=max, min_included=min_included, - max_included=max_included)) + return All( + float_, + Range(min=min, max=max, min_included=min_included, max_included=max_included), + ) port = int_range(min=1, max=65535) @@ -270,29 +411,40 @@ def validate_id_name(value): raise Invalid("ID must not be empty") if value[0].isdigit(): raise Invalid("First character in ID cannot be a digit.") - if '-' in value: - raise Invalid("Dashes are not supported in IDs, please use underscores instead.") - valid_chars = ascii_letters + digits + '_' + if "-" in value: + raise Invalid( + "Dashes are not supported in IDs, please use underscores instead." + ) + valid_chars = ascii_letters + digits + "_" for char in value: if char not in valid_chars: - raise Invalid("IDs must only consist of upper/lowercase characters, the underscore" - "character and numbers. The character '{}' cannot be used" - "".format(char)) + raise Invalid( + "IDs must only consist of upper/lowercase characters, the underscore" + "character and numbers. The character '{}' cannot be used" + "".format(char) + ) if value in RESERVED_IDS: raise Invalid(f"ID '{value}' is reserved internally and cannot be used") if value in CORE.loaded_integrations: - raise Invalid("ID '{}' conflicts with the name of an esphome integration, please use " - "another ID name.".format(value)) + raise Invalid( + "ID '{}' conflicts with the name of an esphome integration, please use " + "another ID name.".format(value) + ) return value def use_id(type): """Declare that this configuration option should point to an ID with the given type.""" + def validator(value): check_not_templatable(value) if value is None: return core.ID(None, is_declaration=False, type=type) - if isinstance(value, core.ID) and value.is_declaration is False and value.type is type: + if ( + isinstance(value, core.ID) + and value.is_declaration is False + and value.type is type + ): return value return core.ID(validate_id_name(value), is_declaration=False, type=type) @@ -306,6 +458,7 @@ def declare_id(type): If two IDs with the same name exist, a validation error is thrown. """ + def validator(value): check_not_templatable(value) if value is None: @@ -348,8 +501,8 @@ def validator_(obj): return validator_ -only_on_esp32 = only_on('ESP32') -only_on_esp8266 = only_on('ESP8266') +only_on_esp32 = only_on("ESP32") +only_on_esp8266 = only_on("ESP8266") # Adapted from: @@ -360,10 +513,10 @@ def has_at_least_one_key(*keys): def validate(obj): """Test keys exist in dict.""" if not isinstance(obj, dict): - raise Invalid('expected dictionary') + raise Invalid("expected dictionary") if not any(k in keys for k in obj): - raise Invalid('Must contain at least one of {}.'.format(', '.join(keys))) + raise Invalid("Must contain at least one of {}.".format(", ".join(keys))) return obj return validate @@ -371,15 +524,16 @@ def validate(obj): def has_exactly_one_key(*keys): """Validate that exactly one of the given keys exist in the config.""" + def validate(obj): if not isinstance(obj, dict): - raise Invalid('expected dictionary') + raise Invalid("expected dictionary") number = sum(k in keys for k in obj) if number > 1: - raise Invalid("Cannot specify more than one of {}.".format(', '.join(keys))) + raise Invalid("Cannot specify more than one of {}.".format(", ".join(keys))) if number < 1: - raise Invalid('Must contain exactly one of {}.'.format(', '.join(keys))) + raise Invalid("Must contain exactly one of {}.".format(", ".join(keys))) return obj return validate @@ -387,43 +541,50 @@ def validate(obj): def has_at_most_one_key(*keys): """Validate that at most one of the given keys exist in the config.""" + def validate(obj): if not isinstance(obj, dict): - raise Invalid('expected dictionary') + raise Invalid("expected dictionary") number = sum(k in keys for k in obj) if number > 1: - raise Invalid("Cannot specify more than one of {}.".format(', '.join(keys))) + raise Invalid("Cannot specify more than one of {}.".format(", ".join(keys))) return obj return validate -TIME_PERIOD_ERROR = "Time period {} should be format number + unit, for example 5ms, 5s, 5min, 5h" +TIME_PERIOD_ERROR = ( + "Time period {} should be format number + unit, for example 5ms, 5s, 5min, 5h" +) time_period_dict = All( - Schema({ - Optional('days'): float_, - Optional('hours'): float_, - Optional('minutes'): float_, - Optional('seconds'): float_, - Optional('milliseconds'): float_, - Optional('microseconds'): float_, - }), - has_at_least_one_key('days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds'), - lambda value: TimePeriod(**value) + Schema( + { + Optional("days"): float_, + Optional("hours"): float_, + Optional("minutes"): float_, + Optional("seconds"): float_, + Optional("milliseconds"): float_, + Optional("microseconds"): float_, + } + ), + has_at_least_one_key( + "days", "hours", "minutes", "seconds", "milliseconds", "microseconds" + ), + lambda value: TimePeriod(**value), ) def time_period_str_colon(value): """Validate and transform time offset with format HH:MM[:SS].""" if isinstance(value, int): - raise Invalid('Make sure you wrap time values in quotes') + raise Invalid("Make sure you wrap time values in quotes") if not isinstance(value, str): raise Invalid(TIME_PERIOD_ERROR.format(value)) try: - parsed = [int(x) for x in value.split(':')] + parsed = [int(x) for x in value.split(":")] except ValueError: # pylint: disable=raise-missing-from raise Invalid(TIME_PERIOD_ERROR.format(value)) @@ -444,34 +605,35 @@ def time_period_str_unit(value): check_not_templatable(value) if isinstance(value, int): - raise Invalid("Don't know what '{0}' means as it has no time *unit*! Did you mean " - "'{0}s'?".format(value)) + raise Invalid( + "Don't know what '{0}' means as it has no time *unit*! Did you mean " + "'{0}s'?".format(value) + ) if isinstance(value, TimePeriod): value = str(value) if not isinstance(value, str): raise Invalid("Expected string for time period with unit.") unit_to_kwarg = { - 'us': 'microseconds', - 'microseconds': 'microseconds', - 'ms': 'milliseconds', - 'milliseconds': 'milliseconds', - 's': 'seconds', - 'sec': 'seconds', - 'seconds': 'seconds', - 'min': 'minutes', - 'minutes': 'minutes', - 'h': 'hours', - 'hours': 'hours', - 'd': 'days', - 'days': 'days', + "us": "microseconds", + "microseconds": "microseconds", + "ms": "milliseconds", + "milliseconds": "milliseconds", + "s": "seconds", + "sec": "seconds", + "seconds": "seconds", + "min": "minutes", + "minutes": "minutes", + "h": "hours", + "hours": "hours", + "d": "days", + "days": "days", } match = re.match(r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*)$", value) if match is None: - raise Invalid("Expected time period with unit, " - "got {}".format(value)) + raise Invalid("Expected time period with unit, " "got {}".format(value)) kwarg = unit_to_kwarg[one_of(*unit_to_kwarg)(match.group(2))] return TimePeriod(**{kwarg: float(match.group(1))}) @@ -506,29 +668,34 @@ def time_period_in_minutes_(value): def update_interval(value): - if value == 'never': + if value == "never": return 4294967295 # uint32_t max return positive_time_period_milliseconds(value) time_period = Any(time_period_str_unit, time_period_str_colon, time_period_dict) positive_time_period = All(time_period, Range(min=TimePeriod())) -positive_time_period_milliseconds = All(positive_time_period, time_period_in_milliseconds_) +positive_time_period_milliseconds = All( + positive_time_period, time_period_in_milliseconds_ +) positive_time_period_seconds = All(positive_time_period, time_period_in_seconds_) positive_time_period_minutes = All(positive_time_period, time_period_in_minutes_) time_period_microseconds = All(time_period, time_period_in_microseconds_) -positive_time_period_microseconds = All(positive_time_period, time_period_in_microseconds_) -positive_not_null_time_period = All(time_period, - Range(min=TimePeriod(), min_included=False)) +positive_time_period_microseconds = All( + positive_time_period, time_period_in_microseconds_ +) +positive_not_null_time_period = All( + time_period, Range(min=TimePeriod(), min_included=False) +) def time_of_day(value): value = string(value) try: - date = datetime.strptime(value, '%H:%M:%S') + date = datetime.strptime(value, "%H:%M:%S") except ValueError as err: try: - date = datetime.strptime(value, '%H:%M:%S %p') + date = datetime.strptime(value, "%H:%M:%S %p") except ValueError: # pylint: disable=raise-missing-from raise Invalid(f"Invalid time of day: {err}") @@ -542,7 +709,7 @@ def time_of_day(value): def mac_address(value): value = string_strict(value) - parts = value.split(':') + parts = value.split(":") if len(parts) != 6: raise Invalid("MAC Address must consist of 6 : (colon) separated parts") parts_int = [] @@ -560,7 +727,7 @@ def mac_address(value): def bind_key(value): value = string_strict(value) - parts = [value[i:i+2] for i in range(0, len(value), 2)] + parts = [value[i : i + 2] for i in range(0, len(value), 2)] if len(parts) != 16: raise Invalid("Bind key must consist of 16 hexadecimal numbers") parts_int = [] @@ -573,7 +740,7 @@ def bind_key(value): # pylint: disable=raise-missing-from raise Invalid("Bind key must be hex values from 00 to FF") - return ''.join(f'{part:02X}' for part in parts_int) + return "".join(f"{part:02X}" for part in parts_int) def uuid(value): @@ -581,14 +748,30 @@ def uuid(value): METRIC_SUFFIXES = { - 'E': 1e18, 'P': 1e15, 'T': 1e12, 'G': 1e9, 'M': 1e6, 'k': 1e3, 'da': 10, 'd': 1e-1, - 'c': 1e-2, 'm': 0.001, 'µ': 1e-6, 'u': 1e-6, 'n': 1e-9, 'p': 1e-12, 'f': 1e-15, 'a': 1e-18, - '': 1 + "E": 1e18, + "P": 1e15, + "T": 1e12, + "G": 1e9, + "M": 1e6, + "k": 1e3, + "da": 10, + "d": 1e-1, + "c": 1e-2, + "m": 0.001, + "µ": 1e-6, + "u": 1e-6, + "n": 1e-9, + "p": 1e-12, + "f": 1e-15, + "a": 1e-18, + "": 1, } def float_with_unit(quantity, regex_suffix, optional_unit=False): - pattern = re.compile(r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*?)" + regex_suffix + r"$", re.UNICODE) + pattern = re.compile( + r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*?)" + regex_suffix + r"$", re.UNICODE + ) def validator(value): if optional_unit: @@ -646,8 +829,8 @@ def temperature(value): raise orig_err # noqa -_color_temperature_mireds = float_with_unit('Color Temperature', r'(mireds|Mireds)') -_color_temperature_kelvin = float_with_unit('Color Temperature', r'(K|Kelvin)') +_color_temperature_mireds = float_with_unit("Color Temperature", r"(mireds|Mireds)") +_color_temperature_kelvin = float_with_unit("Color Temperature", r"(K|Kelvin)") def color_temperature(value): @@ -672,8 +855,10 @@ def validate_bytes(value): raise Invalid("Invalid metric suffix {}".format(match.group(2))) multiplier = METRIC_SUFFIXES[match.group(2)] if multiplier < 1: - raise Invalid("Only suffixes with positive exponents are supported. " - "Got {}".format(match.group(2))) + raise Invalid( + "Only suffixes with positive exponents are supported. " + "Got {}".format(match.group(2)) + ) return int(mantissa * multiplier) @@ -682,7 +867,7 @@ def hostname(value): if len(value) > 63: raise Invalid("Hostnames can only be 63 characters long") for c in value: - if not (c.isalnum() or c in '_-'): + if not (c.isalnum() or c in "_-"): raise Invalid("Hostname can only have alphanumeric characters and _ or -") return value @@ -701,13 +886,15 @@ def domain_name(value): value = string_strict(value) if not value: return value - if not value.startswith('.'): + if not value.startswith("."): raise Invalid("Domain name must start with .") - if value.startswith('..'): + if value.startswith(".."): raise Invalid("Domain name must start with single .") for c in value: - if not (c.isalnum() or c in '._-'): - raise Invalid("Domain name can only have alphanumeric characters and _ or -") + if not (c.isalnum() or c in "._-"): + raise Invalid( + "Domain name can only have alphanumeric characters and _ or -" + ) return value @@ -724,15 +911,13 @@ def ipv4(value): if isinstance(value, list): parts = value elif isinstance(value, str): - parts = value.split('.') + parts = value.split(".") elif isinstance(value, IPAddress): return value else: - raise Invalid("IPv4 address must consist of either string or " - "integer list") + raise Invalid("IPv4 address must consist of either string or " "integer list") if len(parts) != 4: - raise Invalid("IPv4 address must consist of four point-separated " - "integers") + raise Invalid("IPv4 address must consist of four point-separated " "integers") parts_ = list(map(int, parts)) if not all(0 <= x < 256 for x in parts_): raise Invalid("IPv4 address parts must be in range from 0 to 255") @@ -745,38 +930,43 @@ def _valid_topic(value): raise Invalid("Can't use dictionary with topic") value = string(value) try: - raw_value = value.encode('utf-8') + raw_value = value.encode("utf-8") except UnicodeError as err: raise Invalid("MQTT topic name/filter must be valid UTF-8 string.") from err if not raw_value: raise Invalid("MQTT topic name/filter must not be empty.") if len(raw_value) > 65535: - raise Invalid("MQTT topic name/filter must not be longer than " - "65535 encoded bytes.") - if '\0' in value: - raise Invalid("MQTT topic name/filter must not contain null " - "character.") + raise Invalid( + "MQTT topic name/filter must not be longer than " "65535 encoded bytes." + ) + if "\0" in value: + raise Invalid("MQTT topic name/filter must not contain null " "character.") return value def subscribe_topic(value): """Validate that we can subscribe using this MQTT topic.""" value = _valid_topic(value) - for i in (i for i, c in enumerate(value) if c == '+'): - if (i > 0 and value[i - 1] != '/') or \ - (i < len(value) - 1 and value[i + 1] != '/'): - raise Invalid("Single-level wildcard must occupy an entire " - "level of the filter") - - index = value.find('#') + for i in (i for i, c in enumerate(value) if c == "+"): + if (i > 0 and value[i - 1] != "/") or ( + i < len(value) - 1 and value[i + 1] != "/" + ): + raise Invalid( + "Single-level wildcard must occupy an entire " "level of the filter" + ) + + index = value.find("#") if index != -1: if index != len(value) - 1: # If there are multiple wildcards, this will also trigger - raise Invalid("Multi-level wildcard must be the last " - "character in the topic filter.") - if len(value) > 1 and value[index - 1] != '/': - raise Invalid("Multi-level wildcard must be after a topic " - "level separator.") + raise Invalid( + "Multi-level wildcard must be the last " + "character in the topic filter." + ) + if len(value) > 1 and value[index - 1] != "/": + raise Invalid( + "Multi-level wildcard must be after a topic " "level separator." + ) return value @@ -784,14 +974,14 @@ def subscribe_topic(value): def publish_topic(value): """Validate that we can publish using this MQTT topic.""" value = _valid_topic(value) - if '+' in value or '#' in value: + if "+" in value or "#" in value: raise Invalid("Wildcards can not be used in topic names") return value def mqtt_payload(value): if value is None: - return '' + return "" return string(value) @@ -838,7 +1028,7 @@ def possibly_negative_percentage(value): has_percent_sign = False if isinstance(value, str): try: - if value.endswith('%'): + if value.endswith("%"): has_percent_sign = False value = float(value[:-1].rstrip()) / 100.0 else: @@ -860,7 +1050,7 @@ def possibly_negative_percentage(value): def percentage_int(value): - if isinstance(value, str) and value.endswith('%'): + if isinstance(value, str) and value.endswith("%"): value = int(value[:-1].rstrip()) return value @@ -869,6 +1059,7 @@ def invalid(message): """Mark this value as invalid. Each time *any* value is passed here it will result in a validation error with the given message. """ + def validator(value): raise Invalid(message) @@ -920,20 +1111,20 @@ def one_of(*values, **kwargs): - *float* (``bool``, default=False): Whether to convert the incoming values to floats. - *space* (``str``, default=' '): What to convert spaces in the input string to. """ - options = ', '.join(f"'{x}'" for x in values) - lower = kwargs.pop('lower', False) - upper = kwargs.pop('upper', False) - string_ = kwargs.pop('string', False) or lower or upper - to_int = kwargs.pop('int', False) - to_float = kwargs.pop('float', False) - space = kwargs.pop('space', ' ') + options = ", ".join(f"'{x}'" for x in values) + lower = kwargs.pop("lower", False) + upper = kwargs.pop("upper", False) + string_ = kwargs.pop("string", False) or lower or upper + to_int = kwargs.pop("int", False) + to_float = kwargs.pop("float", False) + space = kwargs.pop("space", " ") if kwargs: raise ValueError def validator(value): if string_: value = string(value) - value = value.replace(' ', space) + value = value.replace(" ", space) if to_int: value = int_(value) if to_float: @@ -944,12 +1135,15 @@ def validator(value): value = Upper(value) if value not in values: import difflib + options_ = [str(x) for x in values] option = str(value) matches = difflib.get_close_matches(option, options_) if matches: - raise Invalid("Unknown value '{}', did you mean {}?" - "".format(value, ", ".join(f"'{x}'" for x in matches))) + raise Invalid( + "Unknown value '{}', did you mean {}?" + "".format(value, ", ".join(f"'{x}'" for x in matches)) + ) raise Invalid(f"Unknown value '{value}', valid options are {options}.") return value @@ -976,21 +1170,24 @@ def validator(value): return validator -LAMBDA_ENTITY_ID_PROG = re.compile(r'id\(\s*([a-zA-Z0-9_]+\.[.a-zA-Z0-9_]+)\s*\)') +LAMBDA_ENTITY_ID_PROG = re.compile(r"id\(\s*([a-zA-Z0-9_]+\.[.a-zA-Z0-9_]+)\s*\)") def lambda_(value): """Coerce this configuration option to a lambda.""" if not isinstance(value, Lambda): - value = Lambda(string_strict(value)) + value = make_data_base(Lambda(string_strict(value)), value) entity_id_parts = re.split(LAMBDA_ENTITY_ID_PROG, value.value) if len(entity_id_parts) != 1: - entity_ids = ' '.join("'{}'".format(entity_id_parts[i]) - for i in range(1, len(entity_id_parts), 2)) - raise Invalid("Lambda contains reference to entity-id-style ID {}. " - "The id() wrapper only works for ESPHome-internal types. For importing " - "states from Home Assistant use the 'homeassistant' sensor platforms." - "".format(entity_ids)) + entity_ids = " ".join( + "'{}'".format(entity_id_parts[i]) for i in range(1, len(entity_id_parts), 2) + ) + raise Invalid( + "Lambda contains reference to entity-id-style ID {}. " + "The id() wrapper only works for ESPHome-internal types. For importing " + "states from Home Assistant use the 'homeassistant' sensor platforms." + "".format(entity_ids) + ) return value @@ -1000,18 +1197,22 @@ def returning_lambda(value): Additionally, make sure the lambda returns something. """ value = lambda_(value) - if 'return' not in value.value: - raise Invalid("Lambda doesn't contain a 'return' statement, but the lambda " - "is expected to return a value. \n" - "Please make sure the lambda contains at least one " - "return statement.") + if "return" not in value.value: + raise Invalid( + "Lambda doesn't contain a 'return' statement, but the lambda " + "is expected to return a value. \n" + "Please make sure the lambda contains at least one " + "return statement." + ) return value def dimensions(value): if isinstance(value, list): if len(value) != 2: - raise Invalid("Dimensions must have a length of two, not {}".format(len(value))) + raise Invalid( + "Dimensions must have a length of two, not {}".format(len(value)) + ) try: width, height = int(value[0]), int(value[1]) except ValueError: @@ -1023,65 +1224,91 @@ def dimensions(value): value = string(value) match = re.match(r"\s*([0-9]+)\s*[xX]\s*([0-9]+)\s*", value) if not match: - raise Invalid("Invalid value '{}' for dimensions. Only WIDTHxHEIGHT is allowed.") + raise Invalid( + "Invalid value '{}' for dimensions. Only WIDTHxHEIGHT is allowed." + ) return dimensions([match.group(1), match.group(2)]) def directory(value): import json + value = string(value) path = CORE.relative_config_path(value) - if CORE.vscode and (not CORE.ace or - os.path.abspath(path) == os.path.abspath(CORE.config_path)): - print(json.dumps({ - 'type': 'check_directory_exists', - 'path': path, - })) + if CORE.vscode and ( + not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path) + ): + print( + json.dumps( + { + "type": "check_directory_exists", + "path": path, + } + ) + ) data = json.loads(input()) - assert data['type'] == 'directory_exists_response' - if data['content']: + assert data["type"] == "directory_exists_response" + if data["content"]: return value - raise Invalid("Could not find directory '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path))) + raise Invalid( + "Could not find directory '{}'. Please make sure it exists (full path: {})." + "".format(path, os.path.abspath(path)) + ) if not os.path.exists(path): - raise Invalid("Could not find directory '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path))) + raise Invalid( + "Could not find directory '{}'. Please make sure it exists (full path: {})." + "".format(path, os.path.abspath(path)) + ) if not os.path.isdir(path): - raise Invalid("Path '{}' is not a directory (full path: {})." - "".format(path, os.path.abspath(path))) + raise Invalid( + "Path '{}' is not a directory (full path: {})." + "".format(path, os.path.abspath(path)) + ) return value def file_(value): import json + value = string(value) path = CORE.relative_config_path(value) - if CORE.vscode and (not CORE.ace or - os.path.abspath(path) == os.path.abspath(CORE.config_path)): - print(json.dumps({ - 'type': 'check_file_exists', - 'path': path, - })) + if CORE.vscode and ( + not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path) + ): + print( + json.dumps( + { + "type": "check_file_exists", + "path": path, + } + ) + ) data = json.loads(input()) - assert data['type'] == 'file_exists_response' - if data['content']: + assert data["type"] == "file_exists_response" + if data["content"]: return value - raise Invalid("Could not find file '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path))) + raise Invalid( + "Could not find file '{}'. Please make sure it exists (full path: {})." + "".format(path, os.path.abspath(path)) + ) if not os.path.exists(path): - raise Invalid("Could not find file '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path))) + raise Invalid( + "Could not find file '{}'. Please make sure it exists (full path: {})." + "".format(path, os.path.abspath(path)) + ) if not os.path.isfile(path): - raise Invalid("Path '{}' is not a file (full path: {})." - "".format(path, os.path.abspath(path))) + raise Invalid( + "Path '{}' is not a file (full path: {})." + "".format(path, os.path.abspath(path)) + ) return value -ENTITY_ID_CHARACTERS = 'abcdefghijklmnopqrstuvwxyz0123456789_' +ENTITY_ID_CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789_" def entity_id(value): @@ -1090,9 +1317,9 @@ def entity_id(value): Should only be used for 'homeassistant' platforms. """ value = string_strict(value).lower() - if value.count('.') != 1: + if value.count(".") != 1: raise Invalid("Entity ID must have exactly one dot in it") - for x in value.split('.'): + for x in value.split("."): for c in x: if c not in ENTITY_ID_CHARACTERS: raise Invalid(f"Invalid character for entity ID: {c}") @@ -1116,9 +1343,10 @@ def extract_keys(schema): return keys +@jschema_typed def typed_schema(schemas, **kwargs): """Create a schema that has a key to distinguish between schemas""" - key = kwargs.pop('key', CONF_TYPE) + key = kwargs.pop("key", CONF_TYPE) key_validator = one_of(*schemas, **kwargs) def validator(value): @@ -1175,10 +1403,11 @@ def __init__(self, key, component, default=None): @property def default(self): # pylint: disable=unsupported-membership-test - if (self._component in CORE.raw_config or - (CONF_PACKAGES in CORE.raw_config and - self._component in - {list(x.keys())[0] for x in CORE.raw_config[CONF_PACKAGES].values()})): + if self._component in CORE.raw_config or ( + CONF_PACKAGES in CORE.raw_config + and self._component + in {list(x.keys())[0] for x in CORE.raw_config[CONF_PACKAGES].values()} + ): return self._default return vol.UNDEFINED @@ -1208,17 +1437,23 @@ def ensure_schema(schema): def validate_registry_entry(name, registry): - base_schema = ensure_schema(registry.base_schema).extend({ - Optional(CONF_TYPE_ID): valid, - }, extra=ALLOW_EXTRA) + base_schema = ensure_schema(registry.base_schema).extend( + { + Optional(CONF_TYPE_ID): valid, + }, + extra=ALLOW_EXTRA, + ) ignore_keys = extract_keys(base_schema) + @jschema_registry(registry) def validator(value): if isinstance(value, str): value = {value: {}} if not isinstance(value, dict): - raise Invalid("{} must consist of key-value mapping! Got {}" - "".format(name.title(), value)) + raise Invalid( + "{} must consist of key-value mapping! Got {}" + "".format(name.title(), value) + ) key = next((x for x in value if x not in ignore_keys), None) if key is None: raise Invalid(f"Key missing from {name}! Got {value}") @@ -1226,9 +1461,11 @@ def validator(value): raise Invalid(f"Unable to find {name} with the name '{key}'", [key]) key2 = next((x for x in value if x != key and x not in ignore_keys), None) if key2 is not None: - raise Invalid("Cannot have two {0}s in one item. Key '{1}' overrides '{2}'! " - "Did you forget to indent the block inside the {0}?" - "".format(name, key, key2)) + raise Invalid( + "Cannot have two {0}s in one item. Key '{1}' overrides '{2}'! " + "Did you forget to indent the block inside the {0}?" + "".format(name, key, key2) + ) if value[key] is None: value[key] = {} @@ -1241,9 +1478,9 @@ def validator(value): value[key] = registry_entry.schema(value[key]) if registry_entry.type_id is not None: - my_base_schema = base_schema.extend({ - GenerateID(CONF_TYPE_ID): declare_id(registry_entry.type_id) - }) + my_base_schema = base_schema.extend( + {GenerateID(CONF_TYPE_ID): declare_id(registry_entry.type_id)} + ) value = my_base_schema(value) return value @@ -1255,8 +1492,9 @@ def validate_registry(name, registry): return ensure_list(validate_registry_entry(name, registry)) +@jschema_composite def maybe_simple_value(*validators, **kwargs): - key = kwargs.pop('key', CONF_VALUE) + key = kwargs.pop("key", CONF_VALUE) validator = All(*validators) def validate(value): @@ -1267,30 +1505,35 @@ def validate(value): return validate -MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema({ - Required(CONF_TOPIC): subscribe_topic, - Optional(CONF_PAYLOAD_AVAILABLE, default='online'): mqtt_payload, - Optional(CONF_PAYLOAD_NOT_AVAILABLE, default='offline'): mqtt_payload, -}) - -MQTT_COMPONENT_SCHEMA = Schema({ - Optional(CONF_NAME): string, - Optional(CONF_RETAIN): All(requires_component('mqtt'), boolean), - Optional(CONF_DISCOVERY): All(requires_component('mqtt'), boolean), - Optional(CONF_STATE_TOPIC): All(requires_component('mqtt'), publish_topic), - Optional(CONF_AVAILABILITY): All(requires_component('mqtt'), - Any(None, MQTT_COMPONENT_AVAILABILITY_SCHEMA)), - Optional(CONF_INTERNAL): boolean, -}) +MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema( + { + Required(CONF_TOPIC): subscribe_topic, + Optional(CONF_PAYLOAD_AVAILABLE, default="online"): mqtt_payload, + Optional(CONF_PAYLOAD_NOT_AVAILABLE, default="offline"): mqtt_payload, + } +) + +MQTT_COMPONENT_SCHEMA = Schema( + { + Optional(CONF_NAME): string, + Optional(CONF_RETAIN): All(requires_component("mqtt"), boolean), + Optional(CONF_DISCOVERY): All(requires_component("mqtt"), boolean), + Optional(CONF_STATE_TOPIC): All(requires_component("mqtt"), publish_topic), + Optional(CONF_AVAILABILITY): All( + requires_component("mqtt"), Any(None, MQTT_COMPONENT_AVAILABILITY_SCHEMA) + ), + Optional(CONF_INTERNAL): boolean, + } +) MQTT_COMPONENT_SCHEMA.add_extra(_nameable_validator) -MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend({ - Optional(CONF_COMMAND_TOPIC): All(requires_component('mqtt'), subscribe_topic), -}) +MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( + { + Optional(CONF_COMMAND_TOPIC): All(requires_component("mqtt"), subscribe_topic), + } +) -COMPONENT_SCHEMA = Schema({ - Optional(CONF_SETUP_PRIORITY): float_ -}) +COMPONENT_SCHEMA = Schema({Optional(CONF_SETUP_PRIORITY): float_}) def polling_component_schema(default_update_interval): @@ -1300,10 +1543,16 @@ def polling_component_schema(default_update_interval): :param default_update_interval: The default update interval to set for the integration. """ if default_update_interval is None: - return COMPONENT_SCHEMA.extend({ - Required(CONF_UPDATE_INTERVAL): default_update_interval, - }) + return COMPONENT_SCHEMA.extend( + { + Required(CONF_UPDATE_INTERVAL): default_update_interval, + } + ) assert isinstance(default_update_interval, str) - return COMPONENT_SCHEMA.extend({ - Optional(CONF_UPDATE_INTERVAL, default=default_update_interval): update_interval, - }) + return COMPONENT_SCHEMA.extend( + { + Optional( + CONF_UPDATE_INTERVAL, default=default_update_interval + ): update_interval, + } + ) diff --git a/esphome/const.py b/esphome/const.py index 5313fe5413e5..f8b7da066468 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,665 +1,714 @@ """Constants used by esphome.""" MAJOR_VERSION = 1 -MINOR_VERSION = 16 -PATCH_VERSION = '2' -__short_version__ = f'{MAJOR_VERSION}.{MINOR_VERSION}' -__version__ = f'{__short_version__}.{PATCH_VERSION}' +MINOR_VERSION = 17 +PATCH_VERSION = "0" +__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" +__version__ = f"{__short_version__}.{PATCH_VERSION}" -ESP_PLATFORM_ESP32 = 'ESP32' -ESP_PLATFORM_ESP8266 = 'ESP8266' +ESP_PLATFORM_ESP32 = "ESP32" +ESP_PLATFORM_ESP8266 = "ESP8266" ESP_PLATFORMS = [ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266] -ALLOWED_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_-' +ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789_-" # Lookup table from ESP32 arduino framework version to latest platformio # package with that version # See also https://github.com/platformio/platform-espressif32/releases ARDUINO_VERSION_ESP32 = { - 'dev': 'https://github.com/platformio/platform-espressif32.git', - '1.0.4': 'espressif32@1.12.4', - '1.0.3': 'espressif32@1.10.0', - '1.0.2': 'espressif32@1.9.0', - '1.0.1': 'espressif32@1.7.0', - '1.0.0': 'espressif32@1.5.0', + "dev": "https://github.com/platformio/platform-espressif32.git", + "1.0.5": "platformio/espressif32@3.1.1", + "1.0.4": "platformio/espressif32@3.0.0", + "1.0.3": "platformio/espressif32@1.10.0", + "1.0.2": "platformio/espressif32@1.9.0", + "1.0.1": "platformio/espressif32@1.7.0", + "1.0.0": "platformio/espressif32@1.5.0", } # See also https://github.com/platformio/platform-espressif8266/releases ARDUINO_VERSION_ESP8266 = { - 'dev': 'https://github.com/platformio/platform-espressif8266.git', - '2.7.4': 'espressif8266@2.6.2', - '2.7.3': 'espressif8266@2.6.1', - '2.7.2': 'espressif8266@2.6.0', - '2.7.1': 'espressif8266@2.5.3', - '2.7.0': 'espressif8266@2.5.0', - '2.6.3': 'espressif8266@2.4.0', - '2.6.2': 'espressif8266@2.3.1', - '2.6.1': 'espressif8266@2.3.0', - '2.5.2': 'espressif8266@2.2.3', - '2.5.1': 'espressif8266@2.1.1', - '2.5.0': 'espressif8266@2.0.4', - '2.4.2': 'espressif8266@1.8.0', - '2.4.1': 'espressif8266@1.7.3', - '2.4.0': 'espressif8266@1.6.0', - '2.3.0': 'espressif8266@1.5.0', + "dev": "https://github.com/platformio/platform-espressif8266.git", + "2.7.4": "platformio/espressif8266@2.6.2", + "2.7.3": "platformio/espressif8266@2.6.1", + "2.7.2": "platformio/espressif8266@2.6.0", + "2.7.1": "platformio/espressif8266@2.5.3", + "2.7.0": "platformio/espressif8266@2.5.0", + "2.6.3": "platformio/espressif8266@2.4.0", + "2.6.2": "platformio/espressif8266@2.3.1", + "2.6.1": "platformio/espressif8266@2.3.0", + "2.5.2": "platformio/espressif8266@2.2.3", + "2.5.1": "platformio/espressif8266@2.1.1", + "2.5.0": "platformio/espressif8266@2.0.4", + "2.4.2": "platformio/espressif8266@1.8.0", + "2.4.1": "platformio/espressif8266@1.7.3", + "2.4.0": "platformio/espressif8266@1.6.0", + "2.3.0": "platformio/espressif8266@1.5.0", } -SOURCE_FILE_EXTENSIONS = {'.cpp', '.hpp', '.h', '.c', '.tcc', '.ino'} -HEADER_FILE_EXTENSIONS = {'.h', '.hpp', '.tcc'} +SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} +HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} -CONF_ABOVE = 'above' -CONF_ACCELERATION = 'acceleration' -CONF_ACCELERATION_X = 'acceleration_x' -CONF_ACCELERATION_Y = 'acceleration_y' -CONF_ACCELERATION_Z = 'acceleration_z' -CONF_ACCURACY = 'accuracy' -CONF_ACCURACY_DECIMALS = 'accuracy_decimals' -CONF_ACTION_ID = 'action_id' -CONF_ADDRESS = 'address' -CONF_ALPHA = 'alpha' -CONF_AND = 'and' -CONF_AP = 'ap' -CONF_ARDUINO_VERSION = 'arduino_version' -CONF_ARGS = 'args' -CONF_ASSUMED_STATE = 'assumed_state' -CONF_AT = 'at' -CONF_ATTENUATION = 'attenuation' -CONF_AUTH = 'auth' -CONF_AUTO_MODE = 'auto_mode' -CONF_AUTOMATION_ID = 'automation_id' -CONF_AVAILABILITY = 'availability' -CONF_AWAY = 'away' -CONF_AWAY_CONFIG = 'away_config' -CONF_BACKLIGHT_PIN = 'backlight_pin' -CONF_BATTERY_LEVEL = 'battery_level' -CONF_BATTERY_VOLTAGE = 'battery_voltage' -CONF_BAUD_RATE = 'baud_rate' -CONF_BELOW = 'below' -CONF_BINARY = 'binary' -CONF_BINARY_SENSOR = 'binary_sensor' -CONF_BINARY_SENSORS = 'binary_sensors' -CONF_BINDKEY = 'bindkey' -CONF_BIRTH_MESSAGE = 'birth_message' -CONF_BIT_DEPTH = 'bit_depth' -CONF_BLUE = 'blue' -CONF_BOARD = 'board' -CONF_BOARD_FLASH_MODE = 'board_flash_mode' -CONF_BRANCH = 'branch' -CONF_BRIGHTNESS = 'brightness' -CONF_BROKER = 'broker' -CONF_BSSID = 'bssid' -CONF_BUFFER_SIZE = 'buffer_size' -CONF_BUILD_PATH = 'build_path' -CONF_BUS_VOLTAGE = 'bus_voltage' -CONF_BUSY_PIN = 'busy_pin' -CONF_CALIBRATE_LINEAR = 'calibrate_linear' -CONF_CALIBRATION = 'calibration' -CONF_CAPACITANCE = 'capacitance' -CONF_CARRIER_DUTY_PERCENT = 'carrier_duty_percent' -CONF_CARRIER_FREQUENCY = 'carrier_frequency' +CONF_ABOVE = "above" +CONF_ACCELERATION = "acceleration" +CONF_ACCELERATION_X = "acceleration_x" +CONF_ACCELERATION_Y = "acceleration_y" +CONF_ACCELERATION_Z = "acceleration_z" +CONF_ACCURACY = "accuracy" +CONF_ACCURACY_DECIMALS = "accuracy_decimals" +CONF_ACTION_ID = "action_id" +CONF_ADDRESS = "address" +CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" +CONF_ALPHA = "alpha" +CONF_AND = "and" +CONF_AP = "ap" +CONF_ARDUINO_VERSION = "arduino_version" +CONF_ARGS = "args" +CONF_ASSUMED_STATE = "assumed_state" +CONF_AT = "at" +CONF_ATTENUATION = "attenuation" +CONF_AUTH = "auth" +CONF_AUTO_MODE = "auto_mode" +CONF_AUTOMATION_ID = "automation_id" +CONF_AVAILABILITY = "availability" +CONF_AWAY = "away" +CONF_AWAY_CONFIG = "away_config" +CONF_BACKLIGHT_PIN = "backlight_pin" +CONF_BATTERY_LEVEL = "battery_level" +CONF_BATTERY_VOLTAGE = "battery_voltage" +CONF_BAUD_RATE = "baud_rate" +CONF_BELOW = "below" +CONF_BINARY = "binary" +CONF_BINARY_SENSOR = "binary_sensor" +CONF_BINARY_SENSORS = "binary_sensors" +CONF_BINDKEY = "bindkey" +CONF_BIRTH_MESSAGE = "birth_message" +CONF_BIT_DEPTH = "bit_depth" +CONF_BLUE = "blue" +CONF_BOARD = "board" +CONF_BOARD_FLASH_MODE = "board_flash_mode" +CONF_BRANCH = "branch" +CONF_BRIGHTNESS = "brightness" +CONF_BROKER = "broker" +CONF_BSSID = "bssid" +CONF_BUFFER_SIZE = "buffer_size" +CONF_BUILD_PATH = "build_path" +CONF_BUS_VOLTAGE = "bus_voltage" +CONF_BUSY_PIN = "busy_pin" +CONF_CALIBRATE_LINEAR = "calibrate_linear" +CONF_CALIBRATION = "calibration" +CONF_CAPACITANCE = "capacitance" +CONF_CARRIER_DUTY_PERCENT = "carrier_duty_percent" +CONF_CARRIER_FREQUENCY = "carrier_frequency" CONF_CERTIFICATE = "certificate" CONF_CERTIFICATE_AUTHORITY = "certificate_authority" -CONF_CHANGE_MODE_EVERY = 'change_mode_every' -CONF_CHANNEL = 'channel' -CONF_CHANNELS = 'channels' -CONF_CHIPSET = 'chipset' -CONF_CLIENT_ID = 'client_id' -CONF_CLK_PIN = 'clk_pin' -CONF_CLOCK_PIN = 'clock_pin' -CONF_CLOSE_ACTION = 'close_action' -CONF_CLOSE_DURATION = 'close_duration' -CONF_CLOSE_ENDSTOP = 'close_endstop' -CONF_CO2 = 'co2' -CONF_CODE = 'code' -CONF_COLD_WHITE = 'cold_white' -CONF_COLD_WHITE_COLOR_TEMPERATURE = 'cold_white_color_temperature' -CONF_COLOR_CORRECT = 'color_correct' -CONF_COLOR_TEMPERATURE = 'color_temperature' -CONF_COLORS = 'colors' -CONF_COMMAND = 'command' -CONF_COMMAND_TOPIC = 'command_topic' -CONF_COMMENT = 'comment' -CONF_COMMIT = 'commit' -CONF_COMPONENT_ID = 'component_id' -CONF_COMPONENTS = 'components' -CONF_CONDITION = 'condition' -CONF_CONDITION_ID = 'condition_id' -CONF_CONDUCTIVITY = 'conductivity' -CONF_CONTRAST = 'contrast' -CONF_COOL_ACTION = 'cool_action' -CONF_COOL_MODE = 'cool_mode' -CONF_COUNT_MODE = 'count_mode' -CONF_CRON = 'cron' -CONF_CS_PIN = 'cs_pin' -CONF_CSS_INCLUDE = 'css_include' -CONF_CSS_URL = 'css_url' -CONF_CURRENT = 'current' -CONF_CURRENT_OPERATION = 'current_operation' -CONF_CURRENT_RESISTOR = 'current_resistor' -CONF_DALLAS_ID = 'dallas_id' -CONF_DATA = 'data' -CONF_DATA_PIN = 'data_pin' -CONF_DATA_PINS = 'data_pins' -CONF_DATA_RATE = 'data_rate' -CONF_DATA_TEMPLATE = 'data_template' -CONF_DAYS_OF_MONTH = 'days_of_month' -CONF_DAYS_OF_WEEK = 'days_of_week' -CONF_DC_PIN = 'dc_pin' -CONF_DEBOUNCE = 'debounce' -CONF_DECELERATION = 'deceleration' -CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = 'default_target_temperature_high' -CONF_DEFAULT_TARGET_TEMPERATURE_LOW = 'default_target_temperature_low' -CONF_DEFAULT_TRANSITION_LENGTH = 'default_transition_length' -CONF_DELAY = 'delay' -CONF_DELTA = 'delta' -CONF_DEVICE = 'device' -CONF_DEVICE_CLASS = 'device_class' -CONF_DIMENSIONS = 'dimensions' -CONF_DIO_PIN = 'dio_pin' -CONF_DIR_PIN = 'dir_pin' -CONF_DIRECTION = 'direction' -CONF_DIRECTION_OUTPUT = 'direction_output' -CONF_DISCOVERY = 'discovery' -CONF_DISCOVERY_PREFIX = 'discovery_prefix' -CONF_DISCOVERY_RETAIN = 'discovery_retain' -CONF_DISTANCE = 'distance' -CONF_DITHER = 'dither' -CONF_DIV_RATIO = 'div_ratio' -CONF_DNS1 = 'dns1' -CONF_DNS2 = 'dns2' -CONF_DOMAIN = 'domain' -CONF_DRY_ACTION = 'dry_action' -CONF_DRY_MODE = 'dry_mode' -CONF_DUMP = 'dump' -CONF_DURATION = 'duration' -CONF_EAP = 'eap' -CONF_ECHO_PIN = 'echo_pin' -CONF_EFFECT = 'effect' -CONF_EFFECTS = 'effects' -CONF_ELSE = 'else' -CONF_ENABLE_PIN = 'enable_pin' -CONF_ENABLE_TIME = 'enable_time' -CONF_ENERGY = 'energy' -CONF_ENTITY_ID = 'entity_id' -CONF_ESP8266_RESTORE_FROM_FLASH = 'esp8266_restore_from_flash' -CONF_ESPHOME = 'esphome' -CONF_ESPHOME_CORE_VERSION = 'esphome_core_version' -CONF_EVENT = 'event' -CONF_EXPIRE_AFTER = 'expire_after' -CONF_EXTERNAL_VCC = 'external_vcc' -CONF_FALLING_EDGE = 'falling_edge' -CONF_FAMILY = 'family' -CONF_FAN_MODE = 'fan_mode' -CONF_FAN_MODE_AUTO_ACTION = 'fan_mode_auto_action' -CONF_FAN_MODE_DIFFUSE_ACTION = 'fan_mode_diffuse_action' -CONF_FAN_MODE_FOCUS_ACTION = 'fan_mode_focus_action' -CONF_FAN_MODE_HIGH_ACTION = 'fan_mode_high_action' -CONF_FAN_MODE_LOW_ACTION = 'fan_mode_low_action' -CONF_FAN_MODE_MEDIUM_ACTION = 'fan_mode_medium_action' -CONF_FAN_MODE_MIDDLE_ACTION = 'fan_mode_middle_action' -CONF_FAN_MODE_OFF_ACTION = 'fan_mode_off_action' -CONF_FAN_MODE_ON_ACTION = 'fan_mode_on_action' -CONF_FAN_ONLY_ACTION = 'fan_only_action' -CONF_FAN_ONLY_MODE = 'fan_only_mode' -CONF_FAST_CONNECT = 'fast_connect' -CONF_FILE = 'file' -CONF_FILTER = 'filter' -CONF_FILTER_OUT = 'filter_out' -CONF_FILTERS = 'filters' -CONF_FLASH_LENGTH = 'flash_length' -CONF_FOR = 'for' -CONF_FORCE_UPDATE = 'force_update' -CONF_FORMALDEHYDE = 'formaldehyde' -CONF_FORMAT = 'format' -CONF_FREQUENCY = 'frequency' -CONF_FROM = 'from' -CONF_FULL_UPDATE_EVERY = 'full_update_every' -CONF_GAIN = 'gain' -CONF_GAMMA_CORRECT = 'gamma_correct' -CONF_GAS_RESISTANCE = 'gas_resistance' -CONF_GATEWAY = 'gateway' -CONF_GLYPHS = 'glyphs' -CONF_GPIO = 'gpio' -CONF_GREEN = 'green' -CONF_GROUP = 'group' -CONF_HARDWARE_UART = 'hardware_uart' -CONF_HEARTBEAT = 'heartbeat' -CONF_HEAT_ACTION = 'heat_action' -CONF_HEAT_MODE = 'heat_mode' -CONF_HEATER = 'heater' -CONF_HIDDEN = 'hidden' -CONF_HIDE_TIMESTAMP = 'hide_timestamp' -CONF_HIGH = 'high' -CONF_HIGH_VOLTAGE_REFERENCE = 'high_voltage_reference' -CONF_HOUR = 'hour' -CONF_HOURS = 'hours' -CONF_HUMIDITY = 'humidity' +CONF_CHANGE_MODE_EVERY = "change_mode_every" +CONF_CHANNEL = "channel" +CONF_CHANNELS = "channels" +CONF_CHIPSET = "chipset" +CONF_CLIENT_ID = "client_id" +CONF_CLK_PIN = "clk_pin" +CONF_CLOCK_PIN = "clock_pin" +CONF_CLOSE_ACTION = "close_action" +CONF_CLOSE_DURATION = "close_duration" +CONF_CLOSE_ENDSTOP = "close_endstop" +CONF_CO2 = "co2" +CONF_CODE = "code" +CONF_COLD_WHITE = "cold_white" +CONF_COLD_WHITE_COLOR_TEMPERATURE = "cold_white_color_temperature" +CONF_COLOR_CORRECT = "color_correct" +CONF_COLOR_TEMPERATURE = "color_temperature" +CONF_COLORS = "colors" +CONF_COMMAND = "command" +CONF_COMMAND_TOPIC = "command_topic" +CONF_COMMENT = "comment" +CONF_COMMIT = "commit" +CONF_COMPONENT_ID = "component_id" +CONF_COMPONENTS = "components" +CONF_CONDITION = "condition" +CONF_CONDITION_ID = "condition_id" +CONF_CONDUCTIVITY = "conductivity" +CONF_CONTRAST = "contrast" +CONF_COOL_ACTION = "cool_action" +CONF_COOL_MODE = "cool_mode" +CONF_COUNT_MODE = "count_mode" +CONF_CRON = "cron" +CONF_CS_PIN = "cs_pin" +CONF_CSS_INCLUDE = "css_include" +CONF_CSS_URL = "css_url" +CONF_CURRENT = "current" +CONF_CURRENT_OPERATION = "current_operation" +CONF_CURRENT_RESISTOR = "current_resistor" +CONF_DALLAS_ID = "dallas_id" +CONF_DATA = "data" +CONF_DATA_PIN = "data_pin" +CONF_DATA_PINS = "data_pins" +CONF_DATA_RATE = "data_rate" +CONF_DATA_TEMPLATE = "data_template" +CONF_DAYS_OF_MONTH = "days_of_month" +CONF_DAYS_OF_WEEK = "days_of_week" +CONF_DC_PIN = "dc_pin" +CONF_DEBOUNCE = "debounce" +CONF_DECELERATION = "deceleration" +CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high" +CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low" +CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length" +CONF_DELAY = "delay" +CONF_DELTA = "delta" +CONF_DEVICE = "device" +CONF_DEVICE_CLASS = "device_class" +CONF_DIMENSIONS = "dimensions" +CONF_DIO_PIN = "dio_pin" +CONF_DIR_PIN = "dir_pin" +CONF_DIRECTION = "direction" +CONF_DIRECTION_OUTPUT = "direction_output" +CONF_DISCOVERY = "discovery" +CONF_DISCOVERY_PREFIX = "discovery_prefix" +CONF_DISCOVERY_RETAIN = "discovery_retain" +CONF_DISTANCE = "distance" +CONF_DITHER = "dither" +CONF_DIV_RATIO = "div_ratio" +CONF_DNS1 = "dns1" +CONF_DNS2 = "dns2" +CONF_DOMAIN = "domain" +CONF_DRY_ACTION = "dry_action" +CONF_DRY_MODE = "dry_mode" +CONF_DUMP = "dump" +CONF_DURATION = "duration" +CONF_EAP = "eap" +CONF_ECHO_PIN = "echo_pin" +CONF_EFFECT = "effect" +CONF_EFFECTS = "effects" +CONF_ELSE = "else" +CONF_ENABLE_PIN = "enable_pin" +CONF_ENABLE_TIME = "enable_time" +CONF_ENERGY = "energy" +CONF_ENTITY_ID = "entity_id" +CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" +CONF_ESPHOME = "esphome" +CONF_EVENT = "event" +CONF_EXPIRE_AFTER = "expire_after" +CONF_EXTERNAL_VCC = "external_vcc" +CONF_FALLING_EDGE = "falling_edge" +CONF_FAMILY = "family" +CONF_FAN_MODE = "fan_mode" +CONF_FAN_MODE_AUTO_ACTION = "fan_mode_auto_action" +CONF_FAN_MODE_DIFFUSE_ACTION = "fan_mode_diffuse_action" +CONF_FAN_MODE_FOCUS_ACTION = "fan_mode_focus_action" +CONF_FAN_MODE_HIGH_ACTION = "fan_mode_high_action" +CONF_FAN_MODE_LOW_ACTION = "fan_mode_low_action" +CONF_FAN_MODE_MEDIUM_ACTION = "fan_mode_medium_action" +CONF_FAN_MODE_MIDDLE_ACTION = "fan_mode_middle_action" +CONF_FAN_MODE_OFF_ACTION = "fan_mode_off_action" +CONF_FAN_MODE_ON_ACTION = "fan_mode_on_action" +CONF_FAN_ONLY_ACTION = "fan_only_action" +CONF_FAN_ONLY_MODE = "fan_only_mode" +CONF_FAST_CONNECT = "fast_connect" +CONF_FILE = "file" +CONF_FILTER = "filter" +CONF_FILTER_OUT = "filter_out" +CONF_FILTERS = "filters" +CONF_FLASH_LENGTH = "flash_length" +CONF_FOR = "for" +CONF_FORCE_UPDATE = "force_update" +CONF_FORMALDEHYDE = "formaldehyde" +CONF_FORMAT = "format" +CONF_FREQUENCY = "frequency" +CONF_FROM = "from" +CONF_FULL_UPDATE_EVERY = "full_update_every" +CONF_GAIN = "gain" +CONF_GAMMA_CORRECT = "gamma_correct" +CONF_GAS_RESISTANCE = "gas_resistance" +CONF_GATEWAY = "gateway" +CONF_GLYPHS = "glyphs" +CONF_GPIO = "gpio" +CONF_GREEN = "green" +CONF_GROUP = "group" +CONF_HARDWARE_UART = "hardware_uart" +CONF_HEARTBEAT = "heartbeat" +CONF_HEAT_ACTION = "heat_action" +CONF_HEAT_MODE = "heat_mode" +CONF_HEATER = "heater" +CONF_HEIGHT = "height" +CONF_HIDDEN = "hidden" +CONF_HIDE_TIMESTAMP = "hide_timestamp" +CONF_HIGH = "high" +CONF_HIGH_VOLTAGE_REFERENCE = "high_voltage_reference" +CONF_HOUR = "hour" +CONF_HOURS = "hours" +CONF_HUMIDITY = "humidity" CONF_HYSTERESIS = "hysteresis" -CONF_I2C = 'i2c' -CONF_I2C_ID = 'i2c_id' -CONF_ICON = 'icon' -CONF_ID = 'id' -CONF_IDENTITY = 'identity' -CONF_IDLE = 'idle' -CONF_IDLE_ACTION = 'idle_action' -CONF_IDLE_LEVEL = 'idle_level' -CONF_IDLE_TIME = 'idle_time' -CONF_IF = 'if' -CONF_IIR_FILTER = 'iir_filter' -CONF_ILLUMINANCE = 'illuminance' -CONF_INCLUDES = 'includes' -CONF_INDEX = 'index' -CONF_INDOOR = 'indoor' -CONF_INITIAL_MODE = 'initial_mode' -CONF_INITIAL_VALUE = 'initial_value' -CONF_INTEGRATION_TIME = 'integration_time' -CONF_INTENSITY = 'intensity' -CONF_INTERLOCK = 'interlock' -CONF_INTERNAL = 'internal' -CONF_INTERNAL_FILTER = 'internal_filter' -CONF_INTERVAL = 'interval' -CONF_INVALID_COOLDOWN = 'invalid_cooldown' -CONF_INVERT = 'invert' -CONF_INVERTED = 'inverted' -CONF_IP_ADDRESS = 'ip_address' -CONF_JS_INCLUDE = 'js_include' -CONF_JS_URL = 'js_url' -CONF_JVC = 'jvc' -CONF_KEEP_ON_TIME = 'keep_on_time' -CONF_KEEPALIVE = 'keepalive' -CONF_KEY = 'key' -CONF_LAMBDA = 'lambda' -CONF_LENGTH = 'length' -CONF_LEVEL = 'level' -CONF_LG = 'lg' -CONF_LIBRARIES = 'libraries' -CONF_LIGHT = 'light' -CONF_LIGHTNING_ENERGY = 'lightning_energy' -CONF_LIGHTNING_THRESHOLD = 'lightning_threshold' -CONF_LOADED_INTEGRATIONS = 'loaded_integrations' -CONF_LOCAL = 'local' -CONF_LOG_TOPIC = 'log_topic' -CONF_LOGGER = 'logger' -CONF_LOGS = 'logs' -CONF_LOW = 'low' -CONF_LOW_VOLTAGE_REFERENCE = 'low_voltage_reference' -CONF_MAC_ADDRESS = 'mac_address' -CONF_MAINS_FILTER = 'mains_filter' -CONF_MAKE_ID = 'make_id' -CONF_MANUAL_IP = 'manual_ip' -CONF_MANUFACTURER_ID = 'manufacturer_id' -CONF_MASK_DISTURBER = 'mask_disturber' -CONF_MAX_CURRENT = 'max_current' -CONF_MAX_DURATION = 'max_duration' -CONF_MAX_LENGTH = 'max_length' -CONF_MAX_LEVEL = 'max_level' -CONF_MAX_POWER = 'max_power' -CONF_MAX_REFRESH_RATE = 'max_refresh_rate' -CONF_MAX_SPEED = 'max_speed' -CONF_MAX_TEMPERATURE = 'max_temperature' -CONF_MAX_VALUE = 'max_value' -CONF_MAX_VOLTAGE = 'max_voltage' -CONF_MEASUREMENT_DURATION = 'measurement_duration' -CONF_MEASUREMENT_SEQUENCE_NUMBER = 'measurement_sequence_number' -CONF_MEDIUM = 'medium' -CONF_MEMORY_BLOCKS = 'memory_blocks' -CONF_METHOD = 'method' -CONF_MIN_LENGTH = 'min_length' -CONF_MIN_LEVEL = 'min_level' -CONF_MIN_POWER = 'min_power' -CONF_MIN_TEMPERATURE = 'min_temperature' -CONF_MIN_VALUE = 'min_value' -CONF_MINUTE = 'minute' -CONF_MINUTES = 'minutes' -CONF_MISO_PIN = 'miso_pin' -CONF_MODE = 'mode' -CONF_MODEL = 'model' -CONF_MOISTURE = 'moisture' -CONF_MONTHS = 'months' -CONF_MOSI_PIN = 'mosi_pin' -CONF_MOTION = 'motion' -CONF_MOVEMENT_COUNTER = 'movement_counter' -CONF_MQTT = 'mqtt' -CONF_MQTT_ID = 'mqtt_id' -CONF_MULTIPLEXER = 'multiplexer' -CONF_MULTIPLY = 'multiply' -CONF_NAME = 'name' -CONF_NBITS = 'nbits' -CONF_NEC = 'nec' -CONF_NETWORKS = 'networks' -CONF_NOISE_LEVEL = 'noise_level' -CONF_NUM_ATTEMPTS = 'num_attempts' -CONF_NUM_CHANNELS = 'num_channels' -CONF_NUM_CHIPS = 'num_chips' -CONF_NUM_LEDS = 'num_leds' -CONF_NUMBER = 'number' -CONF_OFF_MODE = 'off_mode' -CONF_OFFSET = 'offset' -CONF_ON = 'on' -CONF_ON_BLE_ADVERTISE = 'on_ble_advertise' -CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE = 'on_ble_manufacturer_data_advertise' -CONF_ON_BLE_SERVICE_DATA_ADVERTISE = 'on_ble_service_data_advertise' -CONF_ON_BOOT = 'on_boot' -CONF_ON_CLICK = 'on_click' -CONF_ON_DOUBLE_CLICK = 'on_double_click' -CONF_ON_JSON_MESSAGE = 'on_json_message' -CONF_ON_LOOP = 'on_loop' -CONF_ON_MESSAGE = 'on_message' -CONF_ON_MULTI_CLICK = 'on_multi_click' -CONF_ON_PRESS = 'on_press' -CONF_ON_RAW_VALUE = 'on_raw_value' -CONF_ON_RELEASE = 'on_release' -CONF_ON_SHUTDOWN = 'on_shutdown' -CONF_ON_STATE = 'on_state' -CONF_ON_TAG = 'on_tag' -CONF_ON_TIME = 'on_time' -CONF_ON_TIME_SYNC = 'on_time_sync' -CONF_ON_TURN_OFF = 'on_turn_off' -CONF_ON_TURN_ON = 'on_turn_on' -CONF_ON_VALUE = 'on_value' -CONF_ON_VALUE_RANGE = 'on_value_range' -CONF_ONE = 'one' -CONF_OPEN_ACTION = 'open_action' -CONF_OPEN_DRAIN_INTERRUPT = 'open_drain_interrupt' -CONF_OPEN_DURATION = 'open_duration' -CONF_OPEN_ENDSTOP = 'open_endstop' -CONF_OPTIMISTIC = 'optimistic' -CONF_OR = 'or' -CONF_OSCILLATING = 'oscillating' -CONF_OSCILLATION_COMMAND_TOPIC = 'oscillation_command_topic' -CONF_OSCILLATION_OUTPUT = 'oscillation_output' -CONF_OSCILLATION_STATE_TOPIC = 'oscillation_state_topic' -CONF_OTA = 'ota' -CONF_OUTPUT = 'output' -CONF_OUTPUT_ID = 'output_id' -CONF_OUTPUTS = 'outputs' -CONF_OVERSAMPLING = 'oversampling' -CONF_PACKAGES = 'packages' -CONF_PAGE_ID = 'page_id' -CONF_PAGES = 'pages' -CONF_PANASONIC = 'panasonic' -CONF_PASSWORD = 'password' -CONF_PAYLOAD = 'payload' -CONF_PAYLOAD_AVAILABLE = 'payload_available' -CONF_PAYLOAD_NOT_AVAILABLE = 'payload_not_available' -CONF_PERIOD = 'period' -CONF_PHASE_BALANCER = 'phase_balancer' -CONF_PIN = 'pin' -CONF_PIN_A = 'pin_a' -CONF_PIN_B = 'pin_b' -CONF_PIN_C = 'pin_c' -CONF_PIN_D = 'pin_d' -CONF_PINS = 'pins' -CONF_PLATFORM = 'platform' -CONF_PLATFORMIO_OPTIONS = 'platformio_options' -CONF_PM_1_0 = 'pm_1_0' -CONF_PM_10_0 = 'pm_10_0' -CONF_PM_2_5 = 'pm_2_5' -CONF_PM_4_0 = 'pm_4_0' -CONF_PM_SIZE = 'pm_size' -CONF_PMC_0_5 = 'pmc_0_5' -CONF_PMC_1_0 = 'pmc_1_0' -CONF_PMC_10_0 = 'pmc_10_0' -CONF_PMC_2_5 = 'pmc_2_5' -CONF_PMC_4_0 = 'pmc_4_0' -CONF_PORT = 'port' -CONF_POSITION = 'position' -CONF_POSITION_ACTION = 'position_action' -CONF_POWER = 'power' -CONF_POWER_FACTOR = 'power_factor' -CONF_POWER_ON_VALUE = 'power_on_value' -CONF_POWER_SAVE_MODE = 'power_save_mode' -CONF_POWER_SUPPLY = 'power_supply' -CONF_PRESSURE = 'pressure' -CONF_PRIORITY = 'priority' -CONF_PROTOCOL = 'protocol' -CONF_PULL_MODE = 'pull_mode' -CONF_PULSE_LENGTH = 'pulse_length' -CONF_QOS = 'qos' -CONF_RANDOM = 'random' -CONF_RANGE = 'range' -CONF_RANGE_FROM = 'range_from' -CONF_RANGE_TO = 'range_to' -CONF_RATE = 'rate' -CONF_RAW = 'raw' -CONF_RC_CODE_1 = 'rc_code_1' -CONF_RC_CODE_2 = 'rc_code_2' -CONF_REBOOT_TIMEOUT = 'reboot_timeout' -CONF_RECEIVE_TIMEOUT = 'receive_timeout' -CONF_RED = 'red' -CONF_REFERENCE_RESISTANCE = 'reference_resistance' -CONF_REFERENCE_TEMPERATURE = 'reference_temperature' -CONF_REPEAT = 'repeat' -CONF_REPOSITORY = 'repository' -CONF_RESET_PIN = 'reset_pin' -CONF_RESIZE = 'resize' -CONF_RESOLUTION = 'resolution' -CONF_RESTORE = 'restore' -CONF_RESTORE_MODE = 'restore_mode' -CONF_RESTORE_STATE = 'restore_state' -CONF_RESTORE_VALUE = 'restore_value' -CONF_RETAIN = 'retain' -CONF_RGB_ORDER = 'rgb_order' -CONF_RGBW = 'rgbw' -CONF_RISING_EDGE = 'rising_edge' -CONF_ROTATION = 'rotation' -CONF_RS_PIN = 'rs_pin' -CONF_RTD_NOMINAL_RESISTANCE = 'rtd_nominal_resistance' -CONF_RTD_WIRES = 'rtd_wires' -CONF_RUN_CYCLES = 'run_cycles' -CONF_RUN_DURATION = 'run_duration' -CONF_RW_PIN = 'rw_pin' -CONF_RX_BUFFER_SIZE = 'rx_buffer_size' -CONF_RX_ONLY = 'rx_only' -CONF_RX_PIN = 'rx_pin' -CONF_SAFE_MODE = 'safe_mode' -CONF_SAMSUNG = 'samsung' -CONF_SCAN = 'scan' -CONF_SCL = 'scl' -CONF_SCL_PIN = 'scl_pin' -CONF_SDA = 'sda' -CONF_SDO_PIN = 'sdo_pin' -CONF_SECOND = 'second' -CONF_SECONDS = 'seconds' -CONF_SEGMENTS = 'segments' -CONF_SEL_PIN = 'sel_pin' -CONF_SEND_EVERY = 'send_every' -CONF_SEND_FIRST_AT = 'send_first_at' -CONF_SENSOR = 'sensor' -CONF_SENSOR_ID = 'sensor_id' -CONF_SENSORS = 'sensors' -CONF_SEQUENCE = 'sequence' -CONF_SERVERS = 'servers' -CONF_SERVICE = 'service' -CONF_SERVICE_UUID = 'service_uuid' -CONF_SERVICES = 'services' -CONF_SETUP_MODE = 'setup_mode' -CONF_SETUP_PRIORITY = 'setup_priority' -CONF_SHUNT_RESISTANCE = 'shunt_resistance' -CONF_SHUNT_VOLTAGE = 'shunt_voltage' -CONF_SHUTDOWN_MESSAGE = 'shutdown_message' -CONF_SIZE = 'size' -CONF_SLEEP_DURATION = 'sleep_duration' -CONF_SLEEP_PIN = 'sleep_pin' -CONF_SLEEP_WHEN_DONE = 'sleep_when_done' -CONF_SONY = 'sony' -CONF_SPEED = 'speed' -CONF_SPEED_COMMAND_TOPIC = 'speed_command_topic' -CONF_SPEED_STATE_TOPIC = 'speed_state_topic' -CONF_SPI_ID = 'spi_id' -CONF_SPIKE_REJECTION = 'spike_rejection' -CONF_SSID = 'ssid' -CONF_SSL_FINGERPRINTS = 'ssl_fingerprints' -CONF_STATE = 'state' -CONF_STATE_TOPIC = 'state_topic' -CONF_STATIC_IP = 'static_ip' -CONF_STEP_MODE = 'step_mode' -CONF_STEP_PIN = 'step_pin' -CONF_STOP = 'stop' -CONF_STOP_ACTION = 'stop_action' -CONF_SUBNET = 'subnet' -CONF_SUBSTITUTIONS = 'substitutions' -CONF_SUPPORTS_COOL = 'supports_cool' -CONF_SUPPORTS_HEAT = 'supports_heat' -CONF_SWING_BOTH_ACTION = 'swing_both_action' -CONF_SWING_HORIZONTAL_ACTION = 'swing_horizontal_action' -CONF_SWING_MODE = 'swing_mode' -CONF_SWING_OFF_ACTION = 'swing_off_action' -CONF_SWING_VERTICAL_ACTION = 'swing_vertical_action' +CONF_I2C = "i2c" +CONF_I2C_ID = "i2c_id" +CONF_ICON = "icon" +CONF_ID = "id" +CONF_IDENTITY = "identity" +CONF_IDLE = "idle" +CONF_IDLE_ACTION = "idle_action" +CONF_IDLE_LEVEL = "idle_level" +CONF_IDLE_TIME = "idle_time" +CONF_IF = "if" +CONF_IIR_FILTER = "iir_filter" +CONF_ILLUMINANCE = "illuminance" +CONF_IMPEDANCE = "impedance" +CONF_INCLUDES = "includes" +CONF_INDEX = "index" +CONF_INDOOR = "indoor" +CONF_INITIAL_MODE = "initial_mode" +CONF_INITIAL_VALUE = "initial_value" +CONF_INTEGRATION_TIME = "integration_time" +CONF_INTENSITY = "intensity" +CONF_INTERLOCK = "interlock" +CONF_INTERNAL = "internal" +CONF_INTERNAL_FILTER = "internal_filter" +CONF_INTERRUPT = "interrupt" +CONF_INTERVAL = "interval" +CONF_INVALID_COOLDOWN = "invalid_cooldown" +CONF_INVERT = "invert" +CONF_INVERTED = "inverted" +CONF_IP_ADDRESS = "ip_address" +CONF_JS_INCLUDE = "js_include" +CONF_JS_URL = "js_url" +CONF_JVC = "jvc" +CONF_KEEP_ON_TIME = "keep_on_time" +CONF_KEEPALIVE = "keepalive" +CONF_KEY = "key" +CONF_LAMBDA = "lambda" +CONF_LENGTH = "length" +CONF_LEVEL = "level" +CONF_LG = "lg" +CONF_LIBRARIES = "libraries" +CONF_LIGHT = "light" +CONF_LIGHTNING_ENERGY = "lightning_energy" +CONF_LIGHTNING_THRESHOLD = "lightning_threshold" +CONF_LOADED_INTEGRATIONS = "loaded_integrations" +CONF_LOCAL = "local" +CONF_LOG_TOPIC = "log_topic" +CONF_LOGGER = "logger" +CONF_LOGS = "logs" +CONF_LOW = "low" +CONF_LOW_VOLTAGE_REFERENCE = "low_voltage_reference" +CONF_MAC_ADDRESS = "mac_address" +CONF_MAINS_FILTER = "mains_filter" +CONF_MAKE_ID = "make_id" +CONF_MANUAL_IP = "manual_ip" +CONF_MANUFACTURER_ID = "manufacturer_id" +CONF_MASK_DISTURBER = "mask_disturber" +CONF_MAX_CURRENT = "max_current" +CONF_MAX_DURATION = "max_duration" +CONF_MAX_LENGTH = "max_length" +CONF_MAX_LEVEL = "max_level" +CONF_MAX_POWER = "max_power" +CONF_MAX_REFRESH_RATE = "max_refresh_rate" +CONF_MAX_SPEED = "max_speed" +CONF_MAX_TEMPERATURE = "max_temperature" +CONF_MAX_VALUE = "max_value" +CONF_MAX_VOLTAGE = "max_voltage" +CONF_MEASUREMENT_DURATION = "measurement_duration" +CONF_MEASUREMENT_SEQUENCE_NUMBER = "measurement_sequence_number" +CONF_MEDIUM = "medium" +CONF_MEMORY_BLOCKS = "memory_blocks" +CONF_METHOD = "method" +CONF_MIN_LENGTH = "min_length" +CONF_MIN_LEVEL = "min_level" +CONF_MIN_POWER = "min_power" +CONF_MIN_TEMPERATURE = "min_temperature" +CONF_MIN_VALUE = "min_value" +CONF_MINUTE = "minute" +CONF_MINUTES = "minutes" +CONF_MISO_PIN = "miso_pin" +CONF_MODE = "mode" +CONF_MODEL = "model" +CONF_MOISTURE = "moisture" +CONF_MONTHS = "months" +CONF_MOSI_PIN = "mosi_pin" +CONF_MOTION = "motion" +CONF_MOVEMENT_COUNTER = "movement_counter" +CONF_MQTT = "mqtt" +CONF_MQTT_ID = "mqtt_id" +CONF_MULTIPLEXER = "multiplexer" +CONF_MULTIPLY = "multiply" +CONF_NAME = "name" +CONF_NBITS = "nbits" +CONF_NEC = "nec" +CONF_NETWORKS = "networks" +CONF_NOISE_LEVEL = "noise_level" +CONF_NUM_ATTEMPTS = "num_attempts" +CONF_NUM_CHANNELS = "num_channels" +CONF_NUM_CHIPS = "num_chips" +CONF_NUM_LEDS = "num_leds" +CONF_NUMBER = "number" +CONF_OFF_MODE = "off_mode" +CONF_OFFSET = "offset" +CONF_ON = "on" +CONF_ON_BLE_ADVERTISE = "on_ble_advertise" +CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE = "on_ble_manufacturer_data_advertise" +CONF_ON_BLE_SERVICE_DATA_ADVERTISE = "on_ble_service_data_advertise" +CONF_ON_BOOT = "on_boot" +CONF_ON_CLICK = "on_click" +CONF_ON_DOUBLE_CLICK = "on_double_click" +CONF_ON_JSON_MESSAGE = "on_json_message" +CONF_ON_LOOP = "on_loop" +CONF_ON_MESSAGE = "on_message" +CONF_ON_MULTI_CLICK = "on_multi_click" +CONF_ON_PRESS = "on_press" +CONF_ON_RAW_VALUE = "on_raw_value" +CONF_ON_RELEASE = "on_release" +CONF_ON_SHUTDOWN = "on_shutdown" +CONF_ON_STATE = "on_state" +CONF_ON_TAG = "on_tag" +CONF_ON_TIME = "on_time" +CONF_ON_TIME_SYNC = "on_time_sync" +CONF_ON_TURN_OFF = "on_turn_off" +CONF_ON_TURN_ON = "on_turn_on" +CONF_ON_VALUE = "on_value" +CONF_ON_VALUE_RANGE = "on_value_range" +CONF_ONE = "one" +CONF_OPEN_ACTION = "open_action" +CONF_OPEN_DRAIN_INTERRUPT = "open_drain_interrupt" +CONF_OPEN_DURATION = "open_duration" +CONF_OPEN_ENDSTOP = "open_endstop" +CONF_OPTIMISTIC = "optimistic" +CONF_OR = "or" +CONF_OSCILLATING = "oscillating" +CONF_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic" +CONF_OSCILLATION_OUTPUT = "oscillation_output" +CONF_OSCILLATION_STATE_TOPIC = "oscillation_state_topic" +CONF_OTA = "ota" +CONF_OUTPUT = "output" +CONF_OUTPUT_ID = "output_id" +CONF_OUTPUTS = "outputs" +CONF_OVERSAMPLING = "oversampling" +CONF_PACKAGES = "packages" +CONF_PAGE_ID = "page_id" +CONF_PAGES = "pages" +CONF_PANASONIC = "panasonic" +CONF_PASSWORD = "password" +CONF_PAYLOAD = "payload" +CONF_PAYLOAD_AVAILABLE = "payload_available" +CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available" +CONF_PERIOD = "period" +CONF_PHASE_BALANCER = "phase_balancer" +CONF_PIN = "pin" +CONF_PIN_A = "pin_a" +CONF_PIN_B = "pin_b" +CONF_PIN_C = "pin_c" +CONF_PIN_D = "pin_d" +CONF_PINS = "pins" +CONF_PIXEL_MAPPER = "pixel_mapper" +CONF_PLATFORM = "platform" +CONF_PLATFORMIO_OPTIONS = "platformio_options" +CONF_PM_1_0 = "pm_1_0" +CONF_PM_10_0 = "pm_10_0" +CONF_PM_2_5 = "pm_2_5" +CONF_PM_4_0 = "pm_4_0" +CONF_PM_SIZE = "pm_size" +CONF_PMC_0_5 = "pmc_0_5" +CONF_PMC_1_0 = "pmc_1_0" +CONF_PMC_10_0 = "pmc_10_0" +CONF_PMC_2_5 = "pmc_2_5" +CONF_PMC_4_0 = "pmc_4_0" +CONF_PORT = "port" +CONF_POSITION = "position" +CONF_POSITION_ACTION = "position_action" +CONF_POWER = "power" +CONF_POWER_FACTOR = "power_factor" +CONF_POWER_ON_VALUE = "power_on_value" +CONF_POWER_SAVE_MODE = "power_save_mode" +CONF_POWER_SUPPLY = "power_supply" +CONF_PRESSURE = "pressure" +CONF_PRIORITY = "priority" +CONF_PROTOCOL = "protocol" +CONF_PULL_MODE = "pull_mode" +CONF_PULSE_LENGTH = "pulse_length" +CONF_QOS = "qos" +CONF_RANDOM = "random" +CONF_RANGE = "range" +CONF_RANGE_FROM = "range_from" +CONF_RANGE_TO = "range_to" +CONF_RATE = "rate" +CONF_RAW = "raw" +CONF_RC_CODE_1 = "rc_code_1" +CONF_RC_CODE_2 = "rc_code_2" +CONF_REBOOT_TIMEOUT = "reboot_timeout" +CONF_RECEIVE_TIMEOUT = "receive_timeout" +CONF_RED = "red" +CONF_REFERENCE_RESISTANCE = "reference_resistance" +CONF_REFERENCE_TEMPERATURE = "reference_temperature" +CONF_REPEAT = "repeat" +CONF_REPOSITORY = "repository" +CONF_RESET_PIN = "reset_pin" +CONF_RESIZE = "resize" +CONF_RESOLUTION = "resolution" +CONF_RESTORE = "restore" +CONF_RESTORE_MODE = "restore_mode" +CONF_RESTORE_STATE = "restore_state" +CONF_RESTORE_VALUE = "restore_value" +CONF_RETAIN = "retain" +CONF_RGB_ORDER = "rgb_order" +CONF_RGBW = "rgbw" +CONF_RISING_EDGE = "rising_edge" +CONF_ROTATION = "rotation" +CONF_RS_PIN = "rs_pin" +CONF_RTD_NOMINAL_RESISTANCE = "rtd_nominal_resistance" +CONF_RTD_WIRES = "rtd_wires" +CONF_RUN_CYCLES = "run_cycles" +CONF_RUN_DURATION = "run_duration" +CONF_RW_PIN = "rw_pin" +CONF_RX_BUFFER_SIZE = "rx_buffer_size" +CONF_RX_ONLY = "rx_only" +CONF_RX_PIN = "rx_pin" +CONF_SAFE_MODE = "safe_mode" +CONF_SAMSUNG = "samsung" +CONF_SCAN = "scan" +CONF_SCL = "scl" +CONF_SCL_PIN = "scl_pin" +CONF_SDA = "sda" +CONF_SDO_PIN = "sdo_pin" +CONF_SECOND = "second" +CONF_SECONDS = "seconds" +CONF_SEGMENTS = "segments" +CONF_SEL_PIN = "sel_pin" +CONF_SEND_EVERY = "send_every" +CONF_SEND_FIRST_AT = "send_first_at" +CONF_SENSOR = "sensor" +CONF_SENSOR_ID = "sensor_id" +CONF_SENSORS = "sensors" +CONF_SEQUENCE = "sequence" +CONF_SERVERS = "servers" +CONF_SERVICE = "service" +CONF_SERVICE_UUID = "service_uuid" +CONF_SERVICES = "services" +CONF_SETUP_MODE = "setup_mode" +CONF_SETUP_PRIORITY = "setup_priority" +CONF_SHUNT_RESISTANCE = "shunt_resistance" +CONF_SHUNT_VOLTAGE = "shunt_voltage" +CONF_SHUTDOWN_MESSAGE = "shutdown_message" +CONF_SIZE = "size" +CONF_SLEEP_DURATION = "sleep_duration" +CONF_SLEEP_PIN = "sleep_pin" +CONF_SLEEP_WHEN_DONE = "sleep_when_done" +CONF_SONY = "sony" +CONF_SPEED = "speed" +CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" +CONF_SPEED_COUNT = "speed_count" +CONF_SPEED_STATE_TOPIC = "speed_state_topic" +CONF_SPI_ID = "spi_id" +CONF_SPIKE_REJECTION = "spike_rejection" +CONF_SSID = "ssid" +CONF_SSL_FINGERPRINTS = "ssl_fingerprints" +CONF_STATE = "state" +CONF_STATE_TOPIC = "state_topic" +CONF_STATIC_IP = "static_ip" +CONF_STEP_MODE = "step_mode" +CONF_STEP_PIN = "step_pin" +CONF_STOP = "stop" +CONF_STOP_ACTION = "stop_action" +CONF_SUBNET = "subnet" +CONF_SUBSTITUTIONS = "substitutions" +CONF_SUPPORTS_COOL = "supports_cool" +CONF_SUPPORTS_HEAT = "supports_heat" +CONF_SWING_BOTH_ACTION = "swing_both_action" +CONF_SWING_HORIZONTAL_ACTION = "swing_horizontal_action" +CONF_SWING_MODE = "swing_mode" +CONF_SWING_OFF_ACTION = "swing_off_action" +CONF_SWING_VERTICAL_ACTION = "swing_vertical_action" CONF_SWITCH_DATAPOINT = "switch_datapoint" -CONF_SWITCHES = 'switches' -CONF_SYNC = 'sync' -CONF_TABLET = 'tablet' -CONF_TAG = 'tag' -CONF_TARGET = 'target' -CONF_TARGET_TEMPERATURE = 'target_temperature' -CONF_TARGET_TEMPERATURE_HIGH = 'target_temperature_high' -CONF_TARGET_TEMPERATURE_LOW = 'target_temperature_low' -CONF_TEMPERATURE = 'temperature' -CONF_TEMPERATURE_STEP = 'temperature_step' -CONF_TEXT_SENSORS = 'text_sensors' -CONF_THEN = 'then' -CONF_THRESHOLD = 'threshold' -CONF_THROTTLE = 'throttle' -CONF_TILT = 'tilt' -CONF_TILT_ACTION = 'tilt_action' -CONF_TILT_LAMBDA = 'tilt_lambda' -CONF_TIME = 'time' -CONF_TIME_ID = 'time_id' -CONF_TIMEOUT = 'timeout' -CONF_TIMES = 'times' -CONF_TIMEZONE = 'timezone' -CONF_TIMING = 'timing' -CONF_TO = 'to' -CONF_TOLERANCE = 'tolerance' -CONF_TOPIC = 'topic' -CONF_TOPIC_PREFIX = 'topic_prefix' +CONF_SWITCHES = "switches" +CONF_SYNC = "sync" +CONF_TABLET = "tablet" +CONF_TAG = "tag" +CONF_TARGET = "target" +CONF_TARGET_TEMPERATURE = "target_temperature" +CONF_TARGET_TEMPERATURE_HIGH = "target_temperature_high" +CONF_TARGET_TEMPERATURE_LOW = "target_temperature_low" +CONF_TEMPERATURE = "temperature" +CONF_TEMPERATURE_STEP = "temperature_step" +CONF_TEXT_SENSORS = "text_sensors" +CONF_THEN = "then" +CONF_THRESHOLD = "threshold" +CONF_THROTTLE = "throttle" +CONF_TILT = "tilt" +CONF_TILT_ACTION = "tilt_action" +CONF_TILT_LAMBDA = "tilt_lambda" +CONF_TIME = "time" +CONF_TIME_ID = "time_id" +CONF_TIMEOUT = "timeout" +CONF_TIMES = "times" +CONF_TIMEZONE = "timezone" +CONF_TIMING = "timing" +CONF_TO = "to" +CONF_TOLERANCE = "tolerance" +CONF_TOPIC = "topic" +CONF_TOPIC_PREFIX = "topic_prefix" CONF_TOTAL = "total" -CONF_TRANSITION_LENGTH = 'transition_length' -CONF_TRIGGER_ID = 'trigger_id' -CONF_TRIGGER_PIN = 'trigger_pin' -CONF_TURN_OFF_ACTION = 'turn_off_action' -CONF_TURN_ON_ACTION = 'turn_on_action' -CONF_TX_BUFFER_SIZE = 'tx_buffer_size' -CONF_TX_PIN = 'tx_pin' -CONF_TX_POWER = 'tx_power' -CONF_TYPE = 'type' -CONF_TYPE_ID = 'type_id' -CONF_UART_ID = 'uart_id' -CONF_UID = 'uid' -CONF_UNIQUE = 'unique' -CONF_UNIT_OF_MEASUREMENT = 'unit_of_measurement' -CONF_UPDATE_INTERVAL = 'update_interval' -CONF_UPDATE_ON_BOOT = 'update_on_boot' -CONF_URL = 'url' -CONF_USE_ADDRESS = 'use_address' -CONF_USERNAME = 'username' -CONF_UUID = 'uuid' -CONF_VALUE = 'value' -CONF_VARIABLES = 'variables' -CONF_VARIANT = 'variant' -CONF_VISUAL = 'visual' -CONF_VOLTAGE = 'voltage' -CONF_VOLTAGE_ATTENUATION = 'voltage_attenuation' -CONF_VOLTAGE_DIVIDER = 'voltage_divider' -CONF_WAIT_TIME = 'wait_time' -CONF_WAIT_UNTIL = 'wait_until' -CONF_WAKEUP_PIN = 'wakeup_pin' -CONF_WARM_WHITE = 'warm_white' -CONF_WARM_WHITE_COLOR_TEMPERATURE = 'warm_white_color_temperature' -CONF_WATCHDOG_THRESHOLD = 'watchdog_threshold' -CONF_WHILE = 'while' -CONF_WHITE = 'white' -CONF_WIDTH = 'width' -CONF_WIFI = 'wifi' -CONF_WILL_MESSAGE = 'will_message' -CONF_WIND_DIRECTION_DEGREES = 'wind_direction_degrees' -CONF_WIND_SPEED = 'wind_speed' -CONF_WINDOW_SIZE = 'window_size' -CONF_ZERO = 'zero' +CONF_TRANSITION_LENGTH = "transition_length" +CONF_TRIGGER_ID = "trigger_id" +CONF_TRIGGER_PIN = "trigger_pin" +CONF_TURN_OFF_ACTION = "turn_off_action" +CONF_TURN_ON_ACTION = "turn_on_action" +CONF_TVOC = "tvoc" +CONF_TX_BUFFER_SIZE = "tx_buffer_size" +CONF_TX_PIN = "tx_pin" +CONF_TX_POWER = "tx_power" +CONF_TYPE = "type" +CONF_TYPE_ID = "type_id" +CONF_UART_ID = "uart_id" +CONF_UID = "uid" +CONF_UNIQUE = "unique" +CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement" +CONF_UPDATE_INTERVAL = "update_interval" +CONF_UPDATE_ON_BOOT = "update_on_boot" +CONF_URL = "url" +CONF_USE_ADDRESS = "use_address" +CONF_USERNAME = "username" +CONF_UUID = "uuid" +CONF_VALUE = "value" +CONF_VARIABLES = "variables" +CONF_VARIANT = "variant" +CONF_VISUAL = "visual" +CONF_VOLTAGE = "voltage" +CONF_VOLTAGE_ATTENUATION = "voltage_attenuation" +CONF_VOLTAGE_DIVIDER = "voltage_divider" +CONF_WAIT_TIME = "wait_time" +CONF_WAIT_UNTIL = "wait_until" +CONF_WAKEUP_PIN = "wakeup_pin" +CONF_WARM_WHITE = "warm_white" +CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature" +CONF_WATCHDOG_THRESHOLD = "watchdog_threshold" +CONF_WEIGHT = "weight" +CONF_WHILE = "while" +CONF_WHITE = "white" +CONF_WIDTH = "width" +CONF_WIFI = "wifi" +CONF_WILL_MESSAGE = "will_message" +CONF_WIND_DIRECTION_DEGREES = "wind_direction_degrees" +CONF_WIND_SPEED = "wind_speed" +CONF_WINDOW_SIZE = "window_size" +CONF_ZERO = "zero" -ENV_NOGITIGNORE = 'ESPHOME_NOGITIGNORE' -ENV_QUICKWIZARD = 'ESPHOME_QUICKWIZARD' +ENV_NOGITIGNORE = "ESPHOME_NOGITIGNORE" +ENV_QUICKWIZARD = "ESPHOME_QUICKWIZARD" -ICON_ACCELERATION = 'mdi:axis-arrow' -ICON_ACCELERATION_X = 'mdi:axis-x-arrow' -ICON_ACCELERATION_Y = 'mdi:axis-y-arrow' -ICON_ACCELERATION_Z = 'mdi:axis-z-arrow' -ICON_ARROW_EXPAND_VERTICAL = 'mdi:arrow-expand-vertical' -ICON_BATTERY = 'mdi:battery' -ICON_BRIEFCASE_DOWNLOAD = 'mdi:briefcase-download' -ICON_BRIGHTNESS_5 = 'mdi:brightness-5' -ICON_BUG = 'mdi:bug' -ICON_CHECK_CIRCLE_OUTLINE = 'mdi:check-circle-outline' -ICON_CHEMICAL_WEAPON = 'mdi:chemical-weapon' -ICON_COUNTER = 'mdi:counter' -ICON_CURRENT_AC = 'mdi:current-ac' -ICON_EMPTY = '' -ICON_FLASH = 'mdi:flash' -ICON_FLASK_OUTLINE = 'mdi:flask-outline' -ICON_FLOWER = 'mdi:flower' -ICON_GAS_CYLINDER = 'mdi:gas-cylinder' -ICON_GAUGE = 'mdi:gauge' -ICON_LIGHTBULB = 'mdi:lightbulb' -ICON_MAGNET = 'mdi:magnet' -ICON_MOLECULE_CO2 = 'mdi:molecule-co2' -ICON_MOTION_SENSOR = 'mdi:motion-sensor' -ICON_NEW_BOX = 'mdi:new-box' -ICON_PERCENT = 'mdi:percent' -ICON_POWER = 'mdi:power' -ICON_PULSE = 'mdi:pulse' -ICON_RADIATOR = 'mdi:radiator' -ICON_RESTART = 'mdi:restart' -ICON_ROTATE_RIGHT = 'mdi:rotate-right' -ICON_RULER = 'mdi:ruler' -ICON_SCALE = 'mdi:scale' -ICON_SCREEN_ROTATION = 'mdi:screen-rotation' -ICON_SIGN_DIRECTION = 'mdi:sign-direction' -ICON_SIGNAL = 'mdi:signal-distance-variant' -ICON_SIGNAL_DISTANCE_VARIANT = 'mdi:signal' -ICON_THERMOMETER = 'mdi:thermometer' -ICON_TIMELAPSE = 'mdi:timelapse' -ICON_TIMER = 'mdi:timer-outline' -ICON_WATER_PERCENT = 'mdi:water-percent' -ICON_WEATHER_SUNSET = 'mdi:weather-sunset' -ICON_WEATHER_SUNSET_DOWN = 'mdi:weather-sunset-down' -ICON_WEATHER_SUNSET_UP = 'mdi:weather-sunset-up' -ICON_WEATHER_WINDY = 'mdi:weather-windy' -ICON_WIFI = 'mdi:wifi' +ICON_ACCELERATION = "mdi:axis-arrow" +ICON_ACCELERATION_X = "mdi:axis-x-arrow" +ICON_ACCELERATION_Y = "mdi:axis-y-arrow" +ICON_ACCELERATION_Z = "mdi:axis-z-arrow" +ICON_ARROW_EXPAND_VERTICAL = "mdi:arrow-expand-vertical" +ICON_BATTERY = "mdi:battery" +ICON_BRIEFCASE_DOWNLOAD = "mdi:briefcase-download" +ICON_BRIGHTNESS_5 = "mdi:brightness-5" +ICON_BUG = "mdi:bug" +ICON_CHECK_CIRCLE_OUTLINE = "mdi:check-circle-outline" +ICON_CHEMICAL_WEAPON = "mdi:chemical-weapon" +ICON_COUNTER = "mdi:counter" +ICON_CURRENT_AC = "mdi:current-ac" +ICON_EMPTY = "" +ICON_FLASH = "mdi:flash" +ICON_FLASK = "mdi:flask" +ICON_FLASK_OUTLINE = "mdi:flask-outline" +ICON_FLOWER = "mdi:flower" +ICON_GAS_CYLINDER = "mdi:gas-cylinder" +ICON_GAUGE = "mdi:gauge" +ICON_GRAIN = "mdi:grain" +ICON_LIGHTBULB = "mdi:lightbulb" +ICON_MAGNET = "mdi:magnet" +ICON_MOLECULE_CO2 = "mdi:molecule-co2" +ICON_MOTION_SENSOR = "mdi:motion-sensor" +ICON_NEW_BOX = "mdi:new-box" +ICON_OMEGA = "mdi:omega" +ICON_PERCENT = "mdi:percent" +ICON_POWER = "mdi:power" +ICON_PULSE = "mdi:pulse" +ICON_RADIATOR = "mdi:radiator" +ICON_RESTART = "mdi:restart" +ICON_ROTATE_RIGHT = "mdi:rotate-right" +ICON_RULER = "mdi:ruler" +ICON_SCALE = "mdi:scale" +ICON_SCALE_BATHROOM = "mdi:scale-bathroom" +ICON_SCREEN_ROTATION = "mdi:screen-rotation" +ICON_SIGN_DIRECTION = "mdi:sign-direction" +ICON_SIGNAL = "mdi:signal-distance-variant" +ICON_SIGNAL_DISTANCE_VARIANT = "mdi:signal" +ICON_THERMOMETER = "mdi:thermometer" +ICON_TIMELAPSE = "mdi:timelapse" +ICON_TIMER = "mdi:timer-outline" +ICON_WATER_PERCENT = "mdi:water-percent" +ICON_WEATHER_SUNSET = "mdi:weather-sunset" +ICON_WEATHER_SUNSET_DOWN = "mdi:weather-sunset-down" +ICON_WEATHER_SUNSET_UP = "mdi:weather-sunset-up" +ICON_WEATHER_WINDY = "mdi:weather-windy" +ICON_WIFI = "mdi:wifi" -UNIT_AMPERE = 'A' -UNIT_CELSIUS = '°C' -UNIT_COUNTS_PER_CUBIC_METER = '#/m³' -UNIT_DECIBEL = 'dB' -UNIT_DECIBEL_MILLIWATT = 'dBm' -UNIT_DEGREE_PER_SECOND = '°/s' -UNIT_DEGREES = '°' -UNIT_EMPTY = '' -UNIT_G = 'G' -UNIT_HECTOPASCAL = 'hPa' -UNIT_HERTZ = 'Hz' -UNIT_KELVIN = 'K' -UNIT_KILOMETER = 'km' -UNIT_KILOMETER_PER_HOUR = 'km/h' -UNIT_LUX = 'lx' -UNIT_METER = 'm' -UNIT_METER_PER_SECOND_SQUARED = 'm/s²' -UNIT_MICROGRAMS_PER_CUBIC_METER = 'µg/m³' -UNIT_MICROMETER = 'µm' -UNIT_MICROSIEMENS_PER_CENTIMETER = 'µS/cm' -UNIT_MICROTESLA = 'µT' -UNIT_MILLIGRAMS_PER_CUBIC_METER = 'mg/m³' -UNIT_MINUTE = 'min' -UNIT_OHM = 'Ω' -UNIT_PARTS_PER_BILLION = 'ppb' -UNIT_PARTS_PER_MILLION = 'ppm' -UNIT_PERCENT = '%' +UNIT_AMPERE = "A" +UNIT_CELSIUS = "°C" +UNIT_COUNTS_PER_CUBIC_METER = "#/m³" +UNIT_DECIBEL = "dB" +UNIT_DECIBEL_MILLIWATT = "dBm" +UNIT_DEGREE_PER_SECOND = "°/s" +UNIT_DEGREES = "°" +UNIT_EMPTY = "" +UNIT_G = "G" +UNIT_HECTOPASCAL = "hPa" +UNIT_HERTZ = "Hz" +UNIT_KELVIN = "K" +UNIT_KILOGRAM = "kg" +UNIT_KILOMETER = "km" +UNIT_KILOMETER_PER_HOUR = "km/h" +UNIT_LUX = "lx" +UNIT_METER = "m" +UNIT_METER_PER_SECOND_SQUARED = "m/s²" +UNIT_MICROGRAMS_PER_CUBIC_METER = "µg/m³" +UNIT_MICROMETER = "µm" +UNIT_MICROSIEMENS_PER_CENTIMETER = "µS/cm" +UNIT_MICROTESLA = "µT" +UNIT_MILLIGRAMS_PER_CUBIC_METER = "mg/m³" +UNIT_MINUTE = "min" +UNIT_OHM = "Ω" +UNIT_PARTS_PER_BILLION = "ppb" +UNIT_PARTS_PER_MILLION = "ppm" +UNIT_PERCENT = "%" UNIT_PULSES = "pulses" -UNIT_PULSES_PER_MINUTE = 'pulses/min' -UNIT_SECOND = 's' -UNIT_STEPS = 'steps' -UNIT_VOLT = 'V' -UNIT_VOLT_AMPS = 'VA' -UNIT_VOLT_AMPS_REACTIVE = 'VAR' -UNIT_WATT = 'W' -UNIT_WATT_HOURS = 'Wh' +UNIT_PULSES_PER_MINUTE = "pulses/min" +UNIT_SECOND = "s" +UNIT_STEPS = "steps" +UNIT_VOLT = "V" +UNIT_VOLT_AMPS = "VA" +UNIT_VOLT_AMPS_REACTIVE = "VAR" +UNIT_WATT = "W" +UNIT_WATT_HOURS = "Wh" -DEVICE_CLASS_CONNECTIVITY = 'connectivity' -DEVICE_CLASS_MOVING = 'moving' +# device classes of binary_sensor component +DEVICE_CLASS_BATTERY_CHARGING = "battery_charging" +DEVICE_CLASS_COLD = "cold" +DEVICE_CLASS_CONNECTIVITY = "connectivity" +DEVICE_CLASS_DOOR = "door" +DEVICE_CLASS_GARAGE_DOOR = "garage_door" +DEVICE_CLASS_GAS = "gas" +DEVICE_CLASS_HEAT = "heat" +DEVICE_CLASS_LIGHT = "light" +DEVICE_CLASS_LOCK = "lock" +DEVICE_CLASS_MOISTURE = "moisture" +DEVICE_CLASS_MOTION = "motion" +DEVICE_CLASS_MOVING = "moving" +DEVICE_CLASS_OCCUPANCY = "occupancy" +DEVICE_CLASS_OPENING = "opening" +DEVICE_CLASS_PLUG = "plug" +DEVICE_CLASS_PRESENCE = "presence" +DEVICE_CLASS_PROBLEM = "problem" +DEVICE_CLASS_SAFETY = "safety" +DEVICE_CLASS_SMOKE = "smoke" +DEVICE_CLASS_SOUND = "sound" +DEVICE_CLASS_VIBRATION = "vibration" +DEVICE_CLASS_WINDOW = "window" +# device classes of both binary_sensor and sensor component +DEVICE_CLASS_EMPTY = "" +DEVICE_CLASS_BATTERY = "battery" +DEVICE_CLASS_POWER = "power" +# device classes of sensor component +DEVICE_CLASS_CURRENT = "current" +DEVICE_CLASS_ENERGY = "energy" +DEVICE_CLASS_HUMIDITY = "humidity" +DEVICE_CLASS_ILLUMINANCE = "illuminance" +DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" +DEVICE_CLASS_TEMPERATURE = "temperature" +DEVICE_CLASS_POWER_FACTOR = "power_factor" +DEVICE_CLASS_PRESSURE = "pressure" +DEVICE_CLASS_TIMESTAMP = "timestamp" +DEVICE_CLASS_VOLTAGE = "voltage" diff --git a/esphome/core.py b/esphome/core.py index 0065b750c4aa..cf2a07d35f43 100644 --- a/esphome/core.py +++ b/esphome/core.py @@ -10,8 +10,14 @@ # pylint: disable=unused-import, wrong-import-order from typing import Any, Dict, List, Optional, Set, TYPE_CHECKING # noqa -from esphome.const import CONF_ARDUINO_VERSION, SOURCE_FILE_EXTENSIONS, \ - CONF_COMMENT, CONF_ESPHOME, CONF_USE_ADDRESS, CONF_WIFI +from esphome.const import ( + CONF_ARDUINO_VERSION, + SOURCE_FILE_EXTENSIONS, + CONF_COMMENT, + CONF_ESPHOME, + CONF_USE_ADDRESS, + CONF_WIFI, +) from esphome.helpers import ensure_unique_string, is_hassio from esphome.util import OrderedDict @@ -42,7 +48,7 @@ def __init__(self, *args): self.args = args def __str__(self): - return '.'.join(str(x) for x in self.args) + return ".".join(str(x) for x in self.args) class MACAddress: @@ -52,14 +58,14 @@ def __init__(self, *parts): self.parts = parts def __str__(self): - return ':'.join(f'{part:02X}' for part in self.parts) + return ":".join(f"{part:02X}" for part in self.parts) @property def as_hex(self): from esphome.cpp_generator import RawExpression - num = ''.join(f'{part:02X}' for part in self.parts) - return RawExpression(f'0x{num}ULL') + num = "".join(f"{part:02X}" for part in self.parts) + return RawExpression(f"0x{num}ULL") def is_approximately_integer(value): @@ -69,8 +75,15 @@ def is_approximately_integer(value): class TimePeriod: - def __init__(self, microseconds=None, milliseconds=None, seconds=None, - minutes=None, hours=None, days=None): + def __init__( + self, + microseconds=None, + milliseconds=None, + seconds=None, + minutes=None, + hours=None, + days=None, + ): if days is not None: if not is_approximately_integer(days): frac_days, days = math.modf(days) @@ -121,33 +134,33 @@ def __init__(self, microseconds=None, milliseconds=None, seconds=None, def as_dict(self): out = OrderedDict() if self.microseconds is not None: - out['microseconds'] = self.microseconds + out["microseconds"] = self.microseconds if self.milliseconds is not None: - out['milliseconds'] = self.milliseconds + out["milliseconds"] = self.milliseconds if self.seconds is not None: - out['seconds'] = self.seconds + out["seconds"] = self.seconds if self.minutes is not None: - out['minutes'] = self.minutes + out["minutes"] = self.minutes if self.hours is not None: - out['hours'] = self.hours + out["hours"] = self.hours if self.days is not None: - out['days'] = self.days + out["days"] = self.days return out def __str__(self): if self.microseconds is not None: - return f'{self.total_microseconds}us' + return f"{self.total_microseconds}us" if self.milliseconds is not None: - return f'{self.total_milliseconds}ms' + return f"{self.total_milliseconds}ms" if self.seconds is not None: - return f'{self.total_seconds}s' + return f"{self.total_seconds}s" if self.minutes is not None: - return f'{self.total_minutes}min' + return f"{self.total_minutes}min" if self.hours is not None: - return f'{self.total_hours}h' + return f"{self.total_hours}h" if self.days is not None: - return f'{self.total_days}d' - return '0s' + return f"{self.total_days}d" + return "0s" def __repr__(self): return f"TimePeriod<{self.total_microseconds}>" @@ -223,7 +236,7 @@ class TimePeriodMinutes(TimePeriod): pass -LAMBDA_PROG = re.compile(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)') +LAMBDA_PROG = re.compile(r"id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)") class Lambda: @@ -240,12 +253,13 @@ def __init__(self, value): def comment_remover(self, text): def replacer(match): s = match.group(0) - if s.startswith('/'): + if s.startswith("/"): return " " # note: a space and not an empty string return s + pattern = re.compile( r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', - re.DOTALL | re.MULTILINE + re.DOTALL | re.MULTILINE, ) return re.sub(pattern, replacer, text) @@ -258,7 +272,9 @@ def parts(self): @property def requires_ids(self): if self._requires_ids is None: - self._requires_ids = [ID(self.parts[i]) for i in range(1, len(self.parts), 3)] + self._requires_ids = [ + ID(self.parts[i]) for i in range(1, len(self.parts), 3) + ] return self._requires_ids @property @@ -275,7 +291,7 @@ def __str__(self): return self.value def __repr__(self): - return f'Lambda<{self.value}>' + return f"Lambda<{self.value}>" class ID: @@ -286,26 +302,28 @@ def __init__(self, id, is_declaration=False, type=None, is_manual=None): else: self.is_manual = is_manual self.is_declaration = is_declaration - self.type: Optional['MockObjClass'] = type + self.type: Optional["MockObjClass"] = type def resolve(self, registered_ids): from esphome.config_validation import RESERVED_IDS if self.id is None: - base = str(self.type).replace('::', '_').lower() - name = ''.join(c for c in base if c.isalnum() or c == '_') + base = str(self.type).replace("::", "_").lower() + name = "".join(c for c in base if c.isalnum() or c == "_") used = set(registered_ids) | set(RESERVED_IDS) self.id = ensure_unique_string(name, used) return self.id def __str__(self): if self.id is None: - return '' + return "" return self.id def __repr__(self): - return (f'ID<{self.id} declaration={self.is_declaration}, ' - f'type={self.type}, manual={self.is_manual}>') + return ( + f"ID<{self.id} declaration={self.is_declaration}, " + f"type={self.type}, manual={self.is_manual}>" + ) def __eq__(self, other): if isinstance(other, ID): @@ -316,8 +334,12 @@ def __hash__(self): return hash(self.id) def copy(self): - return ID(self.id, is_declaration=self.is_declaration, type=self.type, - is_manual=self.is_manual) + return ID( + self.id, + is_declaration=self.is_declaration, + type=self.type, + is_manual=self.is_manual, + ) class DocumentLocation: @@ -328,14 +350,15 @@ def __init__(self, document: str, line: int, column: int): @classmethod def from_mark(cls, mark): - return cls( - mark.name, - mark.line, - mark.column - ) + return cls(mark.name, mark.line, mark.column) def __str__(self): - return f'{self.document} {self.line}:{self.column}' + return f"{self.document} {self.line}:{self.column}" + + @property + def as_line_directive(self): + document_path = str(self.document).replace("\\", "\\\\") + return f'#line {self.line + 1} "{document_path}"' class DocumentRange: @@ -346,12 +369,11 @@ def __init__(self, start_mark: DocumentLocation, end_mark: DocumentLocation): @classmethod def from_marks(cls, start_mark, end_mark): return cls( - DocumentLocation.from_mark(start_mark), - DocumentLocation.from_mark(end_mark) + DocumentLocation.from_mark(start_mark), DocumentLocation.from_mark(end_mark) ) def __str__(self): - return f'[{self.start_mark} - {self.end_mark}]' + return f"[{self.start_mark} - {self.end_mark}]" class Define: @@ -362,14 +384,14 @@ def __init__(self, name, value=None): @property def as_build_flag(self): if self.value is None: - return f'-D{self.name}' - return f'-D{self.name}={self.value}' + return f"-D{self.name}" + return f"-D{self.name}={self.value}" @property def as_macro(self): if self.value is None: - return f'#define {self.name}' - return f'#define {self.name} {self.value}' + return f"#define {self.name}" + return f"#define {self.name} {self.value}" @property def as_tuple(self): @@ -393,7 +415,7 @@ def __init__(self, name, version): def as_lib_dep(self): if self.version is None: return self.name - return f'{self.name}@{self.version}' + return f"{self.name}@{self.version}" @property def as_tuple(self): @@ -414,13 +436,13 @@ def coroutine(func): def coroutine_with_priority(priority): def decorator(func): - if getattr(func, '_esphome_coroutine', False): + if getattr(func, "_esphome_coroutine", False): # If func is already a coroutine, do not re-wrap it (performance) return func @functools.wraps(func) def _wrapper_generator(*args, **kwargs): - instance_id = kwargs.pop('__esphome_coroutine_instance__') + instance_id = kwargs.pop("__esphome_coroutine_instance__") if not inspect.isgeneratorfunction(func): # If func is not a generator, return result immediately yield func(*args, **kwargs) @@ -450,8 +472,9 @@ def _wrapper_generator(*args, **kwargs): @functools.wraps(func) def wrapper(*args, **kwargs): import random - instance_id = random.randint(0, 2**32) - kwargs['__esphome_coroutine_instance__'] = instance_id + + instance_id = random.randint(0, 2 ** 32) + kwargs["__esphome_coroutine_instance__"] = instance_id gen = _wrapper_generator(*args, **kwargs) # pylint: disable=protected-access CORE._add_active_coroutine(instance_id, gen) @@ -461,6 +484,7 @@ def wrapper(*args, **kwargs): wrapper._esphome_coroutine = True wrapper.priority = priority return wrapper + return decorator @@ -506,17 +530,17 @@ def __init__(self): # Task counter for pending tasks self.task_counter = 0 # The variable cache, for each ID this holds a MockObj of the variable obj - self.variables: Dict[str, 'MockObj'] = {} + self.variables: Dict[str, "MockObj"] = {} # A list of statements that go in the main setup() block - self.main_statements: List['Statement'] = [] + self.main_statements: List["Statement"] = [] # A list of statements to insert in the global block (includes and global variables) - self.global_statements: List['Statement'] = [] + self.global_statements: List["Statement"] = [] # A set of platformio libraries to add to the project self.libraries: List[Library] = [] # A set of build flags to set in the platformio project self.build_flags: Set[str] = set() # A set of defines to set for the compile process in esphome/core/defines.h - self.defines: Set['Define'] = set() + self.defines: Set["Define"] = set() # A dictionary of started coroutines, used to warn when a coroutine was not # awaited. self.active_coroutines: Dict[int, Any] = {} @@ -553,11 +577,11 @@ def address(self) -> Optional[str]: if self.config is None: raise ValueError("Config has not been loaded yet") - if 'wifi' in self.config: + if "wifi" in self.config: return self.config[CONF_WIFI][CONF_USE_ADDRESS] - if 'ethernet' in self.config: - return self.config['ethernet'][CONF_USE_ADDRESS] + if "ethernet" in self.config: + return self.config["ethernet"][CONF_USE_ADDRESS] return None @@ -603,33 +627,33 @@ def relative_build_path(self, *path): return os.path.join(self.build_path, path_) def relative_src_path(self, *path): - return self.relative_build_path('src', *path) + return self.relative_build_path("src", *path) def relative_pioenvs_path(self, *path): if is_hassio(): - return os.path.join('/data', self.name, '.pioenvs', *path) - return self.relative_build_path('.pioenvs', *path) + return os.path.join("/data", self.name, ".pioenvs", *path) + return self.relative_build_path(".pioenvs", *path) def relative_piolibdeps_path(self, *path): if is_hassio(): - return os.path.join('/data', self.name, '.piolibdeps', *path) - return self.relative_build_path('.piolibdeps', *path) + return os.path.join("/data", self.name, ".piolibdeps", *path) + return self.relative_build_path(".piolibdeps", *path) @property def firmware_bin(self): - return self.relative_pioenvs_path(self.name, 'firmware.bin') + return self.relative_pioenvs_path(self.name, "firmware.bin") @property def is_esp8266(self): if self.esp_platform is None: raise ValueError("No platform specified") - return self.esp_platform == 'ESP8266' + return self.esp_platform == "ESP8266" @property def is_esp32(self): if self.esp_platform is None: raise ValueError("No platform specified") - return self.esp_platform == 'ESP32' + return self.esp_platform == "ESP32" def add_job(self, func, *args, **kwargs): coro = coroutine(func) @@ -662,14 +686,17 @@ def flush_tasks(self): # Print not-awaited coroutines for obj in self.active_coroutines.values(): - _LOGGER.warning("Coroutine '%s' %s was never awaited with 'yield'.", obj.__name__, obj) + _LOGGER.warning( + "Coroutine '%s' %s was never awaited with 'yield'.", obj.__name__, obj + ) _LOGGER.warning("Please file a bug report with your configuration.") if self.active_coroutines: raise EsphomeError() if self.component_ids: - comps = ', '.join(f"'{x}'" for x in self.component_ids) - _LOGGER.warning("Components %s were never registered. Please create a bug report", - comps) + comps = ", ".join(f"'{x}'" for x in self.component_ids) + _LOGGER.warning( + "Components %s were never registered. Please create a bug report", comps + ) _LOGGER.warning("with your configuration.") raise EsphomeError() self.active_coroutines.clear() @@ -680,8 +707,10 @@ def add(self, expression): if isinstance(expression, Expression): expression = statement(expression) if not isinstance(expression, Statement): - raise ValueError("Add '{}' must be expression or statement, not {}" - "".format(expression, type(expression))) + raise ValueError( + "Add '{}' must be expression or statement, not {}" + "".format(expression, type(expression)) + ) self.main_statements.append(expression) _LOGGER.debug("Adding: %s", expression) @@ -693,16 +722,20 @@ def add_global(self, expression): if isinstance(expression, Expression): expression = statement(expression) if not isinstance(expression, Statement): - raise ValueError("Add '{}' must be expression or statement, not {}" - "".format(expression, type(expression))) + raise ValueError( + "Add '{}' must be expression or statement, not {}" + "".format(expression, type(expression)) + ) self.global_statements.append(expression) _LOGGER.debug("Adding global: %s", expression) return expression def add_library(self, library): if not isinstance(library, Library): - raise ValueError("Library {} must be instance of Library, not {}" - "".format(library, type(library))) + raise ValueError( + "Library {} must be instance of Library, not {}" + "".format(library, type(library)) + ) _LOGGER.debug("Adding library: %s", library) for other in self.libraries[:]: if other.name != library.name: @@ -717,9 +750,11 @@ def add_library(self, library): if other.version == library.version: break - raise ValueError("Version pinning failed! Libraries {} and {} " - "requested with conflicting versions!" - "".format(library, other)) + raise ValueError( + "Version pinning failed! Libraries {} and {} " + "requested with conflicting versions!" + "".format(library, other) + ) else: self.libraries.append(library) return library @@ -735,8 +770,10 @@ def add_define(self, define): elif isinstance(define, Define): pass else: - raise ValueError("Define {} must be string or Define, not {}" - "".format(define, type(define))) + raise ValueError( + "Define {} must be string or Define, not {}" + "".format(define, type(define)) + ) self.defines.add(define) _LOGGER.debug("Adding define: %s", define) return define @@ -779,7 +816,7 @@ def cpp_main_section(self): text = str(statement(exp)) text = text.rstrip() main_code.append(text) - return '\n'.join(main_code) + '\n\n' + return "\n".join(main_code) + "\n\n" @property def cpp_global_section(self): @@ -790,7 +827,7 @@ def cpp_global_section(self): text = str(statement(exp)) text = text.rstrip() global_code.append(text) - return '\n'.join(global_code) + '\n' + return "\n".join(global_code) + "\n" class AutoLoad(OrderedDict): @@ -799,13 +836,14 @@ class AutoLoad(OrderedDict): class EnumValue: """Special type used by ESPHome to mark enum values for cv.enum.""" + @property def enum_value(self): - return getattr(self, '_enum_value', None) + return getattr(self, "_enum_value", None) @enum_value.setter def enum_value(self, value): - setattr(self, '_enum_value', value) + setattr(self, "_enum_value", value) CORE = EsphomeCore() diff --git a/esphome/core/application.h b/esphome/core/application.h index 3c293e6c8fa0..e9c8638f607e 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -37,8 +37,12 @@ namespace esphome { class Application { public: - void pre_setup(const std::string &name, const char *compilation_time) { - this->name_ = name; + void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) { + if (name_add_mac_suffix) { + this->name_ = name + "_" + get_mac_address().substr(6); + } else { + this->name_ = name; + } this->compilation_time_ = compilation_time; global_preferences.begin(); } diff --git a/esphome/core/color.h b/esphome/core/color.h index 3120e48064ea..d2f225dc3a94 100644 --- a/esphome/core/color.h +++ b/esphome/core/color.h @@ -6,7 +6,6 @@ namespace esphome { inline static uint8_t esp_scale8(uint8_t i, uint8_t scale) { return (uint16_t(i) * (1 + uint16_t(scale))) / 256; } -inline static uint8_t esp_scale(uint8_t i, uint8_t scale, uint8_t max_value = 255) { return (max_value * i / scale); } struct Color { union { @@ -31,75 +30,19 @@ struct Color { uint8_t raw[4]; uint32_t raw_32; }; - enum ColorOrder : uint8_t { COLOR_ORDER_RGB = 0, COLOR_ORDER_BGR = 1, COLOR_ORDER_GRB = 2 }; - enum ColorBitness : uint8_t { COLOR_BITNESS_888 = 0, COLOR_BITNESS_565 = 1, COLOR_BITNESS_332 = 2 }; + inline Color() ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT - inline Color(float red, float green, float blue) ALWAYS_INLINE : r(uint8_t(red * 255)), - g(uint8_t(green * 255)), - b(uint8_t(blue * 255)), - w(0) {} - inline Color(float red, float green, float blue, float white) ALWAYS_INLINE : r(uint8_t(red * 255)), - g(uint8_t(green * 255)), - b(uint8_t(blue * 255)), - w(uint8_t(white * 255)) {} + inline Color(uint8_t red, uint8_t green, uint8_t blue) ALWAYS_INLINE : r(red), g(green), b(blue), w(0) {} + + inline Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) ALWAYS_INLINE : r(red), + g(green), + b(blue), + w(white) {} inline Color(uint32_t colorcode) ALWAYS_INLINE : r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF), w((colorcode >> 24) & 0xFF) {} - inline Color(uint32_t colorcode, ColorOrder color_order, ColorBitness color_bitness = ColorBitness::COLOR_BITNESS_888, - bool right_bit_aligned = true) { - uint8_t first_color, second_color, third_color; - uint8_t first_bits = 0; - uint8_t second_bits = 0; - uint8_t third_bits = 0; - - switch (color_bitness) { - case COLOR_BITNESS_888: - first_bits = 8; - second_bits = 8; - third_bits = 8; - break; - case COLOR_BITNESS_565: - first_bits = 5; - second_bits = 6; - third_bits = 5; - break; - case COLOR_BITNESS_332: - first_bits = 3; - second_bits = 3; - third_bits = 2; - break; - } - - first_color = right_bit_aligned ? esp_scale(((colorcode >> (second_bits + third_bits)) & ((1 << first_bits) - 1)), - ((1 << first_bits) - 1)) - : esp_scale(((colorcode >> 16) & 0xFF), (1 << first_bits) - 1); - - second_color = right_bit_aligned - ? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1)) - : esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1)); - third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & 0xFF), ((1 << third_bits) - 1)) - : esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1)); - - switch (color_order) { - case COLOR_ORDER_RGB: - this->r = first_color; - this->g = second_color; - this->b = third_color; - break; - case COLOR_ORDER_BGR: - this->b = first_color; - this->g = second_color; - this->r = third_color; - break; - case COLOR_ORDER_GRB: - this->g = first_color; - this->r = second_color; - this->b = third_color; - break; - } - } inline bool is_on() ALWAYS_INLINE { return this->raw_32 != 0; } inline Color &operator=(const Color &rhs) ALWAYS_INLINE { this->r = rhs.r; @@ -187,65 +130,21 @@ struct Color { } inline Color &operator-=(uint8_t subtract) ALWAYS_INLINE { return *this = (*this) - subtract; } static Color random_color() { - float r = float(random_uint32()) / float(UINT32_MAX); - float g = float(random_uint32()) / float(UINT32_MAX); - float b = float(random_uint32()) / float(UINT32_MAX); - float w = float(random_uint32()) / float(UINT32_MAX); - return Color(r, g, b, w); - } - Color fade_to_white(uint8_t amnt) { return Color(1, 1, 1, 1) - (*this * amnt); } + uint32_t rand = random_uint32(); + uint8_t w = rand >> 24; + uint8_t r = rand >> 16; + uint8_t g = rand >> 8; + uint8_t b = rand >> 0; + const uint16_t max_rgb = std::max(r, std::max(g, b)); + return Color(uint8_t((uint16_t(r) * 255U / max_rgb)), uint8_t((uint16_t(g) * 255U / max_rgb)), + uint8_t((uint16_t(b) * 255U / max_rgb)), w); + } + Color fade_to_white(uint8_t amnt) { return Color(255, 255, 255, 255) - (*this * amnt); } Color fade_to_black(uint8_t amnt) { return *this * amnt; } Color lighten(uint8_t delta) { return *this + delta; } Color darken(uint8_t delta) { return *this - delta; } - uint8_t to_332(ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) const { - uint16_t red_color, green_color, blue_color; - - red_color = esp_scale8(this->red, ((1 << 3) - 1)); - green_color = esp_scale8(this->green, ((1 << 3) - 1)); - blue_color = esp_scale8(this->blue, (1 << 2) - 1); - - switch (color_order) { - case COLOR_ORDER_RGB: - return red_color << 5 | green_color << 2 | blue_color; - case COLOR_ORDER_BGR: - return blue_color << 6 | green_color << 3 | red_color; - case COLOR_ORDER_GRB: - return green_color << 5 | red_color << 2 | blue_color; - } - return 0; - } - uint16_t to_565(ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) const { - uint16_t red_color, green_color, blue_color; - - red_color = esp_scale8(this->red, ((1 << 5) - 1)); - green_color = esp_scale8(this->green, ((1 << 6) - 1)); - blue_color = esp_scale8(this->blue, (1 << 5) - 1); - - switch (color_order) { - case COLOR_ORDER_RGB: - return red_color << 11 | green_color << 5 | blue_color; - case COLOR_ORDER_BGR: - return blue_color << 11 | green_color << 5 | red_color; - case COLOR_ORDER_GRB: - return green_color << 10 | red_color << 5 | blue_color; - } - return 0; - } - uint32_t to_rgb_565() const { - uint32_t color565 = - (esp_scale8(this->red, 31) << 11) | (esp_scale8(this->green, 63) << 5) | (esp_scale8(this->blue, 31) << 0); - return color565; - } - uint32_t to_bgr_565() const { - uint32_t color565 = - (esp_scale8(this->blue, 31) << 11) | (esp_scale8(this->green, 63) << 5) | (esp_scale8(this->red, 31) << 0); - return color565; - } - uint32_t to_grayscale4() const { - uint32_t gs4 = esp_scale8(this->white, 15); - return gs4; - } }; + static const Color COLOR_BLACK(0, 0, 0); -static const Color COLOR_WHITE(1, 1, 1); +static const Color COLOR_WHITE(255, 255, 255, 255); }; // namespace esphome diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 3e7472d2fe3c..db26e62781ba 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -248,6 +248,13 @@ optional parse_float(const std::string &str) { return {}; return value; } +optional parse_int(const std::string &str) { + char *end; + int value = ::strtol(str.c_str(), &end, 10); + if (end == nullptr || end != str.end().base()) + return {}; + return value; +} uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 40e53e601ef1..63706e8a191d 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -42,6 +42,7 @@ std::string to_string(float val); std::string to_string(double val); std::string to_string(long double val); optional parse_float(const std::string &str); +optional parse_int(const std::string &str); /// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars. std::string sanitize_hostname(const std::string &hostname); diff --git a/esphome/core_config.py b/esphome/core_config.py index f1bb18eef592..55d219d86b55 100644 --- a/esphome/core_config.py +++ b/esphome/core_config.py @@ -5,25 +5,47 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation, pins -from esphome.const import CONF_ARDUINO_VERSION, CONF_BOARD, CONF_BOARD_FLASH_MODE, \ - CONF_BUILD_PATH, CONF_COMMENT, CONF_ESPHOME, CONF_INCLUDES, CONF_LIBRARIES, \ - CONF_NAME, CONF_ON_BOOT, CONF_ON_LOOP, CONF_ON_SHUTDOWN, CONF_PLATFORM, \ - CONF_PLATFORMIO_OPTIONS, CONF_PRIORITY, CONF_TRIGGER_ID, \ - CONF_ESP8266_RESTORE_FROM_FLASH, ARDUINO_VERSION_ESP8266, \ - ARDUINO_VERSION_ESP32, ESP_PLATFORMS +from esphome.const import ( + CONF_ARDUINO_VERSION, + CONF_BOARD, + CONF_BOARD_FLASH_MODE, + CONF_BUILD_PATH, + CONF_COMMENT, + CONF_ESPHOME, + CONF_INCLUDES, + CONF_LIBRARIES, + CONF_NAME, + CONF_ON_BOOT, + CONF_ON_LOOP, + CONF_ON_SHUTDOWN, + CONF_PLATFORM, + CONF_PLATFORMIO_OPTIONS, + CONF_PRIORITY, + CONF_TRIGGER_ID, + CONF_ESP8266_RESTORE_FROM_FLASH, + ARDUINO_VERSION_ESP8266, + ARDUINO_VERSION_ESP32, + ESP_PLATFORMS, +) from esphome.core import CORE, coroutine_with_priority from esphome.helpers import copy_file_if_changed, walk_files _LOGGER = logging.getLogger(__name__) -BUILD_FLASH_MODES = ['qio', 'qout', 'dio', 'dout'] -StartupTrigger = cg.esphome_ns.class_('StartupTrigger', cg.Component, automation.Trigger.template()) -ShutdownTrigger = cg.esphome_ns.class_('ShutdownTrigger', cg.Component, - automation.Trigger.template()) -LoopTrigger = cg.esphome_ns.class_('LoopTrigger', cg.Component, - automation.Trigger.template()) +BUILD_FLASH_MODES = ["qio", "qout", "dio", "dout"] +StartupTrigger = cg.esphome_ns.class_( + "StartupTrigger", cg.Component, automation.Trigger.template() +) +ShutdownTrigger = cg.esphome_ns.class_( + "ShutdownTrigger", cg.Component, automation.Trigger.template() +) +LoopTrigger = cg.esphome_ns.class_( + "LoopTrigger", cg.Component, automation.Trigger.template() +) -VERSION_REGEX = re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+(?:[ab]\d+)?$') +VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[ab]\d+)?$") + +CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" def validate_board(value): @@ -35,8 +57,11 @@ def validate_board(value): raise NotImplementedError if value not in board_pins: - raise cv.Invalid("Could not find board '{}'. Valid boards are {}".format( - value, ', '.join(sorted(board_pins.keys())))) + raise cv.Invalid( + "Could not find board '{}'. Valid boards are {}".format( + value, ", ".join(sorted(board_pins.keys())) + ) + ) return value @@ -44,16 +69,16 @@ def validate_board(value): PLATFORMIO_ESP8266_LUT = { **ARDUINO_VERSION_ESP8266, - 'RECOMMENDED': ARDUINO_VERSION_ESP8266['2.7.4'], - 'LATEST': 'espressif8266', - 'DEV': ARDUINO_VERSION_ESP8266['dev'], + "RECOMMENDED": ARDUINO_VERSION_ESP8266["2.7.4"], + "LATEST": "espressif8266", + "DEV": ARDUINO_VERSION_ESP8266["dev"], } PLATFORMIO_ESP32_LUT = { **ARDUINO_VERSION_ESP32, - 'RECOMMENDED': ARDUINO_VERSION_ESP32['1.0.4'], - 'LATEST': 'espressif32', - 'DEV': ARDUINO_VERSION_ESP32['dev'], + "RECOMMENDED": ARDUINO_VERSION_ESP32["1.0.4"], + "LATEST": "espressif32", + "DEV": ARDUINO_VERSION_ESP32["dev"], } @@ -61,18 +86,28 @@ def validate_arduino_version(value): value = cv.string_strict(value) value_ = value.upper() if CORE.is_esp8266: - if VERSION_REGEX.match(value) is not None and value_ not in PLATFORMIO_ESP8266_LUT: - raise cv.Invalid("Unfortunately the arduino framework version '{}' is unsupported " - "at this time. You can override this by manually using " - "espressif8266@".format(value)) + if ( + VERSION_REGEX.match(value) is not None + and value_ not in PLATFORMIO_ESP8266_LUT + ): + raise cv.Invalid( + "Unfortunately the arduino framework version '{}' is unsupported " + "at this time. You can override this by manually using " + "espressif8266@".format(value) + ) if value_ in PLATFORMIO_ESP8266_LUT: return PLATFORMIO_ESP8266_LUT[value_] return value if CORE.is_esp32: - if VERSION_REGEX.match(value) is not None and value_ not in PLATFORMIO_ESP32_LUT: - raise cv.Invalid("Unfortunately the arduino framework version '{}' is unsupported " - "at this time. You can override this by manually using " - "espressif32@".format(value)) + if ( + VERSION_REGEX.match(value) is not None + and value_ not in PLATFORMIO_ESP32_LUT + ): + raise cv.Invalid( + "Unfortunately the arduino framework version '{}' is unsupported " + "at this time. You can override this by manually using " + "espressif32@".format(value) + ) if value_ in PLATFORMIO_ESP32_LUT: return PLATFORMIO_ESP32_LUT[value_] return value @@ -83,7 +118,7 @@ def default_build_path(): return CORE.name -VALID_INCLUDE_EXTS = {'.h', '.hpp', '.tcc', '.ino', '.cpp', '.c'} +VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} def valid_include(value): @@ -94,62 +129,86 @@ def valid_include(value): value = cv.file_(value) _, ext = os.path.splitext(value) if ext not in VALID_INCLUDE_EXTS: - raise cv.Invalid("Include has invalid file extension {} - valid extensions are {}" - "".format(ext, ', '.join(VALID_INCLUDE_EXTS))) + raise cv.Invalid( + "Include has invalid file extension {} - valid extensions are {}" + "".format(ext, ", ".join(VALID_INCLUDE_EXTS)) + ) return value -CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_NAME): cv.valid_name, - cv.Required(CONF_PLATFORM): cv.one_of('ESP8266', 'ESP32', upper=True), - cv.Required(CONF_BOARD): validate_board, - cv.Optional(CONF_COMMENT): cv.string, - cv.Optional(CONF_ARDUINO_VERSION, default='recommended'): validate_arduino_version, - cv.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, - cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema({ - cv.string_strict: cv.Any([cv.string], cv.string), - }), - cv.SplitDefault(CONF_ESP8266_RESTORE_FROM_FLASH, esp8266=False): cv.All(cv.only_on_esp8266, - cv.boolean), - - cv.SplitDefault(CONF_BOARD_FLASH_MODE, esp8266='dout'): cv.one_of(*BUILD_FLASH_MODES, - lower=True), - cv.Optional(CONF_ON_BOOT): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), - cv.Optional(CONF_PRIORITY, default=600.0): cv.float_, - }), - cv.Optional(CONF_ON_SHUTDOWN): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ShutdownTrigger), - }), - cv.Optional(CONF_ON_LOOP): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoopTrigger), - }), - cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include), - cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict), - - cv.Optional('esphome_core_version'): cv.invalid("The esphome_core_version option has been " - "removed in 1.13 - the esphome core source " - "files are now bundled with ESPHome.") -}) - -PRELOAD_CONFIG_SCHEMA = cv.Schema({ - cv.Required(CONF_NAME): cv.valid_name, - cv.Required(CONF_PLATFORM): validate_platform, -}, extra=cv.ALLOW_EXTRA) - -PRELOAD_CONFIG_SCHEMA2 = PRELOAD_CONFIG_SCHEMA.extend({ - cv.Required(CONF_BOARD): validate_board, - cv.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, -}) +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_NAME): cv.valid_name, + cv.Required(CONF_PLATFORM): cv.one_of("ESP8266", "ESP32", upper=True), + cv.Required(CONF_BOARD): validate_board, + cv.Optional(CONF_COMMENT): cv.string, + cv.Optional( + CONF_ARDUINO_VERSION, default="recommended" + ): validate_arduino_version, + cv.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, + cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( + { + cv.string_strict: cv.Any([cv.string], cv.string), + } + ), + cv.SplitDefault(CONF_ESP8266_RESTORE_FROM_FLASH, esp8266=False): cv.All( + cv.only_on_esp8266, cv.boolean + ), + cv.SplitDefault(CONF_BOARD_FLASH_MODE, esp8266="dout"): cv.one_of( + *BUILD_FLASH_MODES, lower=True + ), + cv.Optional(CONF_ON_BOOT): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), + cv.Optional(CONF_PRIORITY, default=600.0): cv.float_, + } + ), + cv.Optional(CONF_ON_SHUTDOWN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ShutdownTrigger), + } + ), + cv.Optional(CONF_ON_LOOP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoopTrigger), + } + ), + cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include), + cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict), + cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean, + cv.Optional("esphome_core_version"): cv.invalid( + "The esphome_core_version option has been " + "removed in 1.13 - the esphome core source " + "files are now bundled with ESPHome." + ), + } +) + +PRELOAD_CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_NAME): cv.valid_name, + cv.Required(CONF_PLATFORM): validate_platform, + }, + extra=cv.ALLOW_EXTRA, +) + +PRELOAD_CONFIG_SCHEMA2 = PRELOAD_CONFIG_SCHEMA.extend( + { + cv.Required(CONF_BOARD): validate_board, + cv.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, + } +) def preload_core_config(config): - core_key = 'esphome' - if 'esphomeyaml' in config: - _LOGGER.warning("The esphomeyaml section has been renamed to esphome in 1.11.0. " - "Please replace 'esphomeyaml:' in your configuration with 'esphome:'.") - config[CONF_ESPHOME] = config.pop('esphomeyaml') - core_key = 'esphomeyaml' + core_key = "esphome" + if "esphomeyaml" in config: + _LOGGER.warning( + "The esphomeyaml section has been renamed to esphome in 1.11.0. " + "Please replace 'esphomeyaml:' in your configuration with 'esphome:'." + ) + config[CONF_ESPHOME] = config.pop("esphomeyaml") + core_key = "esphomeyaml" if CONF_ESPHOME not in config: raise cv.RequiredFieldInvalid("required key not provided", CONF_ESPHOME) with cv.prepend_path(core_key): @@ -168,7 +227,7 @@ def include_file(path, basename): copy_file_if_changed(path, dst) _, ext = os.path.splitext(path) - if ext in ['.h', '.hpp', '.tcc']: + if ext in [".h", ".hpp", ".tcc"]: # Header, add include statement cg.add_global(cg.RawStatement(f'#include "{basename}"')) @@ -192,7 +251,9 @@ def add_includes(includes): @coroutine_with_priority(-1000.0) def _esp8266_add_lwip_type(): # If any component has already set this, do not change it - if any(flag.startswith('-DPIO_FRAMEWORK_ARDUINO_LWIP2_') for flag in CORE.build_flags): + if any( + flag.startswith("-DPIO_FRAMEWORK_ARDUINO_LWIP2_") for flag in CORE.build_flags + ): return # Default for platformio is LWIP2_LOW_MEMORY with: @@ -206,7 +267,7 @@ def _esp8266_add_lwip_type(): # - MSS=1460 # - LWIP_FEATURES disabled (because we don't need them) # Other projects like Tasmota & ESPEasy also use this - cg.add_build_flag('-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH') + cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH") @coroutine_with_priority(30.0) @@ -229,8 +290,14 @@ def _add_automations(config): @coroutine_with_priority(100.0) def to_code(config): - cg.add_global(cg.global_ns.namespace('esphome').using) - cg.add(cg.App.pre_setup(config[CONF_NAME], cg.RawExpression('__DATE__ ", " __TIME__'))) + cg.add_global(cg.global_ns.namespace("esphome").using) + cg.add( + cg.App.pre_setup( + config[CONF_NAME], + cg.RawExpression('__DATE__ ", " __TIME__'), + config[CONF_NAME_ADD_MAC_SUFFIX], + ) + ) CORE.add_job(_add_automations, config) @@ -238,27 +305,27 @@ def to_code(config): if CORE.is_esp8266: CORE.add_job(_esp8266_add_lwip_type) - cg.add_build_flag('-fno-exceptions') + cg.add_build_flag("-fno-exceptions") # Libraries if CORE.is_esp32: - cg.add_library('ESPmDNS', None) + cg.add_library("ESPmDNS", None) elif CORE.is_esp8266: - cg.add_library('ESP8266WiFi', None) - cg.add_library('ESP8266mDNS', None) + cg.add_library("ESP8266WiFi", None) + cg.add_library("ESP8266mDNS", None) for lib in config[CONF_LIBRARIES]: - if '@' in lib: - name, vers = lib.split('@', 1) + if "@" in lib: + name, vers = lib.split("@", 1) cg.add_library(name, vers) else: cg.add_library(lib, None) - cg.add_build_flag('-Wno-unused-variable') - cg.add_build_flag('-Wno-unused-but-set-variable') - cg.add_build_flag('-Wno-sign-compare') + cg.add_build_flag("-Wno-unused-variable") + cg.add_build_flag("-Wno-unused-but-set-variable") + cg.add_build_flag("-Wno-sign-compare") if config.get(CONF_ESP8266_RESTORE_FROM_FLASH, False): - cg.add_define('USE_ESP8266_PREFERENCES_FLASH') + cg.add_define("USE_ESP8266_PREFERENCES_FLASH") if config[CONF_INCLUDES]: CORE.add_job(add_includes, config[CONF_INCLUDES]) diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 3f87257e2628..999b252dde25 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -1,14 +1,27 @@ import abc import inspect import math +import re +from esphome.yaml_util import ESPHomeDataBase # pylint: disable=unused-import, wrong-import-order from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence from esphome.core import ( # noqa - CORE, HexInt, ID, Lambda, TimePeriod, TimePeriodMicroseconds, - TimePeriodMilliseconds, TimePeriodMinutes, TimePeriodSeconds, coroutine, Library, Define, - EnumValue) + CORE, + HexInt, + ID, + Lambda, + TimePeriod, + TimePeriodMicroseconds, + TimePeriodMilliseconds, + TimePeriodMinutes, + TimePeriodSeconds, + coroutine, + Library, + Define, + EnumValue, +) from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last from esphome.util import OrderedDict @@ -23,12 +36,23 @@ def __str__(self): """ -SafeExpType = Union[Expression, bool, str, str, int, float, TimePeriod, - Type[bool], Type[int], Type[float], Sequence[Any]] +SafeExpType = Union[ + Expression, + bool, + str, + str, + int, + float, + TimePeriod, + Type[bool], + Type[int], + Type[float], + Sequence[Any], +] class RawExpression(Expression): - __slots__ = ("text", ) + __slots__ = ("text",) def __init__(self, text: str): self.text = text @@ -66,7 +90,7 @@ def __str__(self): class ExpressionList(Expression): - __slots__ = ("args", ) + __slots__ = ("args",) def __init__(self, *args: Optional[SafeExpType]): # Remove every None on end @@ -84,13 +108,13 @@ def __iter__(self): class TemplateArguments(Expression): - __slots__ = ("args", ) + __slots__ = ("args",) def __init__(self, *args: SafeExpType): self.args = ExpressionList(*args) def __str__(self): - return f'<{self.args}>' + return f"<{self.args}>" def __iter__(self): return iter(self.args) @@ -110,8 +134,8 @@ def __init__(self, base: Expression, *args: SafeExpType): def __str__(self): if self.template_args is not None: - return f'{self.base}{self.template_args}({self.args})' - return f'{self.base}({self.args})' + return f"{self.base}{self.template_args}({self.args})" + return f"{self.base}({self.args})" class StructInitializer(Expression): @@ -130,10 +154,10 @@ def __init__(self, base: Expression, *args: Tuple[str, Optional[SafeExpType]]): self.args[key] = exp def __str__(self): - cpp = f'{self.base}{{\n' + cpp = f"{self.base}{{\n" for key, value in self.args.items(): - cpp += f' .{key} = {value},\n' - cpp += '}' + cpp += f" .{key} = {value},\n" + cpp += "}" return cpp @@ -151,14 +175,14 @@ def __init__(self, *args: Any, multiline: bool = False): def __str__(self): if not self.args: - return '{}' + return "{}" if self.multiline: - cpp = '{\n' + cpp = "{\n" for arg in self.args: - cpp += f' {arg},\n' - cpp += '}' + cpp += f" {arg},\n" + cpp += "}" else: - cpp = '{' + ', '.join(str(arg) for arg in self.args) + '}' + cpp = "{" + ", ".join(str(arg) for arg in self.args) + "}" return cpp @@ -174,9 +198,11 @@ def __str__(self): class ParameterListExpression(Expression): - __slots__ = ("parameters", ) + __slots__ = ("parameters",) - def __init__(self, *parameters: Union[ParameterExpression, Tuple[SafeExpType, str]]): + def __init__( + self, *parameters: Union[ParameterExpression, Tuple[SafeExpType, str]] + ): self.parameters = [] for parameter in parameters: if not isinstance(parameter, ParameterExpression): @@ -188,26 +214,32 @@ def __str__(self): class LambdaExpression(Expression): - __slots__ = ("parts", "parameters", "capture", "return_type") + __slots__ = ("parts", "parameters", "capture", "return_type", "source") - def __init__(self, parts, parameters, capture: str = '=', return_type=None): + def __init__( + self, parts, parameters, capture: str = "=", return_type=None, source=None + ): self.parts = parts if not isinstance(parameters, ParameterListExpression): parameters = ParameterListExpression(*parameters) self.parameters = parameters + self.source = source self.capture = capture self.return_type = safe_exp(return_type) if return_type is not None else None def __str__(self): - cpp = f'[{self.capture}]({self.parameters})' + cpp = f"[{self.capture}]({self.parameters})" if self.return_type is not None: - cpp += f' -> {self.return_type}' - cpp += f' {{\n{self.content}\n}}' + cpp += f" -> {self.return_type}" + cpp += " {\n" + if self.source is not None: + cpp += f"{self.source.as_line_directive}\n" + cpp += f"{self.content}\n}}" return indent_all_but_first_and_last(cpp) @property def content(self): - return ''.join(str(part) for part in self.parts) + return "".join(str(part) for part in self.parts) # pylint: disable=abstract-method @@ -216,7 +248,7 @@ class Literal(Expression, metaclass=abc.ABCMeta): class StringLiteral(Literal): - __slots__ = ("string", ) + __slots__ = ("string",) def __init__(self, string: str): super().__init__() @@ -227,7 +259,7 @@ def __str__(self): class IntLiteral(Literal): - __slots__ = ("i", ) + __slots__ = ("i",) def __init__(self, i: int): super().__init__() @@ -235,16 +267,16 @@ def __init__(self, i: int): def __str__(self): if self.i > 4294967295: - return f'{self.i}ULL' + return f"{self.i}ULL" if self.i > 2147483647: - return f'{self.i}UL' + return f"{self.i}UL" if self.i < -2147483648: - return f'{self.i}LL' + return f"{self.i}LL" return str(self.i) class BoolLiteral(Literal): - __slots__ = ("binary", ) + __slots__ = ("binary",) def __init__(self, binary: bool): super().__init__() @@ -255,7 +287,7 @@ def __str__(self): class HexIntLiteral(Literal): - __slots__ = ("i", ) + __slots__ = ("i",) def __init__(self, i: int): super().__init__() @@ -266,7 +298,7 @@ def __str__(self): class FloatLiteral(Literal): - __slots__ = ("f", ) + __slots__ = ("f",) def __init__(self, value: float): super().__init__() @@ -315,11 +347,15 @@ def safe_exp(obj: SafeExpType) -> Expression: if obj is float: return float_ if isinstance(obj, ID): - raise ValueError("Object {} is an ID. Did you forget to register the variable?" - "".format(obj)) + raise ValueError( + "Object {} is an ID. Did you forget to register the variable?" + "".format(obj) + ) if inspect.isgenerator(obj): - raise ValueError("Object {} is a coroutine. Did you forget to await the expression with " - "'yield'?".format(obj)) + raise ValueError( + "Object {} is a coroutine. Did you forget to await the expression with " + "'yield'?".format(obj) + ) raise ValueError("Object is not an expression", obj) @@ -334,7 +370,7 @@ def __str__(self): class RawStatement(Statement): - __slots__ = ("text", ) + __slots__ = ("text",) def __init__(self, text: str): self.text = text @@ -344,7 +380,7 @@ def __str__(self): class ExpressionStatement(Statement): - __slots__ = ("expression", ) + __slots__ = ("expression",) def __init__(self, expression): self.expression = safe_exp(expression) @@ -354,22 +390,22 @@ def __str__(self): class LineComment(Statement): - __slots__ = ("value", ) + __slots__ = ("value",) def __init__(self, value: str): self.value = value def __str__(self): - parts = self.value.split('\n') - parts = [f'// {x}' for x in parts] - return '\n'.join(parts) + parts = re.sub(r"\\\s*\n", r"\n", self.value, re.MULTILINE).split("\n") + parts = [f"// {x}" for x in parts] + return "\n".join(parts) class ProgmemAssignmentExpression(AssignmentExpression): __slots__ = () def __init__(self, type_, name, rhs, obj): - super().__init__(type_, '', name, rhs, obj) + super().__init__(type_, "", name, rhs, obj) def __str__(self): return f"static const {self.type} {self.name}[] PROGMEM = {self.rhs}" @@ -377,7 +413,7 @@ def __str__(self): def progmem_array(id_, rhs) -> "MockObj": rhs = safe_exp(rhs) - obj = MockObj(id_, '.') + obj = MockObj(id_, ".") assignment = ProgmemAssignmentExpression(id_.type, id_, rhs, obj) CORE.add(assignment) CORE.register_variable(id_, obj) @@ -385,15 +421,35 @@ def progmem_array(id_, rhs) -> "MockObj": def statement(expression: Union[Expression, Statement]) -> Statement: - """Convert expression into a statement unless is already a statement. - """ + """Convert expression into a statement unless is already a statement.""" if isinstance(expression, Statement): return expression return ExpressionStatement(expression) def variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": - """Declare a new variable (not pointer type) in the code generation. + """Declare a new variable, not pointer type, in the code generation. + + :param id_: The ID used to declare the variable. + :param rhs: The expression to place on the right hand side of the assignment. + :param type_: Manually define a type for the variable, only use this when it's not possible + to do so during config validation phase (for example because of template arguments). + + :returns The new variable as a MockObj. + """ + assert isinstance(id_, ID) + rhs = safe_exp(rhs) + obj = MockObj(id_, ".") + if type_ is not None: + id_.type = type_ + assignment = AssignmentExpression(id_.type, "", id_, rhs, obj) + CORE.add(assignment) + CORE.register_variable(id_, obj) + return obj + + +def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": + """Declare and define a new variable, not pointer type, in the code generation. :param id_: The ID used to declare the variable. :param rhs: The expression to place on the right hand side of the assignment. @@ -404,10 +460,12 @@ def variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": """ assert isinstance(id_, ID) rhs = safe_exp(rhs) - obj = MockObj(id_, '.') + obj = MockObj(id_, ".") if type_ is not None: id_.type = type_ - assignment = AssignmentExpression(id_.type, '', id_, rhs, obj) + decl = VariableDeclarationExpression(id_.type, "", id_) + CORE.add_global(decl) + assignment = AssignmentExpression(None, "", id_, rhs, obj) CORE.add(assignment) CORE.register_variable(id_, obj) return obj @@ -424,10 +482,10 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": :returns The new variable as a MockObj. """ rhs = safe_exp(rhs) - obj = MockObj(id_, '->') + obj = MockObj(id_, "->") if type_ is not None: id_.type = type_ - decl = VariableDeclarationExpression(id_.type, '*', id_) + decl = VariableDeclarationExpression(id_.type, "*", id_) CORE.add_global(decl) assignment = AssignmentExpression(None, None, id_, rhs, obj) CORE.add(assignment) @@ -523,8 +581,10 @@ def get_variable_with_full_id(id_: ID) -> Generator[Tuple[ID, "MockObj"], None, @coroutine def process_lambda( - value: Lambda, parameters: List[Tuple[SafeExpType, str]], - capture: str = '=', return_type: SafeExpType = None + value: Lambda, + parameters: List[Tuple[SafeExpType, str]], + capture: str = "=", + return_type: SafeExpType = None, ) -> Generator[LambdaExpression, None, None]: """Process the given lambda value into a LambdaExpression. @@ -545,17 +605,26 @@ def process_lambda( parts = value.parts[:] for i, id in enumerate(value.requires_ids): full_id, var = yield CORE.get_variable_with_full_id(id) - if full_id is not None and isinstance(full_id.type, MockObjClass) and \ - full_id.type.inherits_from(GlobalsComponent): + if ( + full_id is not None + and isinstance(full_id.type, MockObjClass) + and full_id.type.inherits_from(GlobalsComponent) + ): parts[i * 3 + 1] = var.value() continue - if parts[i * 3 + 2] == '.': + if parts[i * 3 + 2] == ".": parts[i * 3 + 1] = var._ else: parts[i * 3 + 1] = var - parts[i * 3 + 2] = '' - yield LambdaExpression(parts, parameters, capture, return_type) + parts[i * 3 + 2] = "" + + if isinstance(value, ESPHomeDataBase) and value.esp_range is not None: + location = value.esp_range.start_mark + location.line += value.content_offset + else: + location = None + yield LambdaExpression(parts, parameters, capture, return_type, location) def is_template(value): @@ -564,10 +633,12 @@ def is_template(value): @coroutine -def templatable(value: Any, - args: List[Tuple[SafeExpType, str]], - output_type: Optional[SafeExpType], - to_exp: Any = None): +def templatable( + value: Any, + args: List[Tuple[SafeExpType, str]], + output_type: Optional[SafeExpType], + to_exp: Any = None, +): """Generate code for a templatable config option. If `value` is a templated value, the lambda expression is returned. @@ -596,20 +667,21 @@ class MockObj(Expression): Mostly consists of magic methods that allow ESPHome's codegen syntax. """ + __slots__ = ("base", "op") - def __init__(self, base, op='.'): + def __init__(self, base, op="."): self.base = base self.op = op def __getattr__(self, attr: str) -> "MockObj": - next_op = '.' - if attr.startswith('P') and self.op not in ['::', '']: + next_op = "." + if attr.startswith("P") and self.op not in ["::", ""]: attr = attr[1:] - next_op = '->' - if attr.startswith('_'): + next_op = "->" + if attr.startswith("_"): attr = attr[1:] - return MockObj(f'{self.base}{self.op}{attr}', next_op) + return MockObj(f"{self.base}{self.op}{attr}", next_op) def __call__(self, *args): # type: (SafeExpType) -> MockObj call = CallExpression(self.base, *args) @@ -619,29 +691,29 @@ def __str__(self): return str(self.base) def __repr__(self): - return 'MockObj<{}>'.format(str(self.base)) + return "MockObj<{}>".format(str(self.base)) @property def _(self) -> "MockObj": - return MockObj(f'{self.base}{self.op}') + return MockObj(f"{self.base}{self.op}") @property def new(self) -> "MockObj": - return MockObj(f'new {self.base}', '->') + return MockObj(f"new {self.base}", "->") def template(self, *args: SafeExpType) -> "MockObj": if len(args) != 1 or not isinstance(args[0], TemplateArguments): args = TemplateArguments(*args) else: args = args[0] - return MockObj(f'{self.base}{args}') + return MockObj(f"{self.base}{args}") def namespace(self, name: str) -> "MockObj": - return MockObj(f'{self._}{name}', '::') + return MockObj(f"{self._}{name}", "::") def class_(self, name: str, *parents: "MockObjClass") -> "MockObjClass": - op = '' if self.op == '' else '::' - return MockObjClass(f'{self.base}{op}{name}', '.', parents=parents) + op = "" if self.op == "" else "::" + return MockObjClass(f"{self.base}{op}{name}", ".", parents=parents) def struct(self, name: str) -> "MockObjClass": return self.class_(name) @@ -650,50 +722,50 @@ def enum(self, name: str, is_class: bool = False) -> "MockObj": return MockObjEnum(enum=name, is_class=is_class, base=self.base, op=self.op) def operator(self, name: str) -> "MockObj": - if name == 'ref': - return MockObj(f'{self.base} &', '') - if name == 'ptr': - return MockObj(f'{self.base} *', '') + if name == "ref": + return MockObj(f"{self.base} &", "") + if name == "ptr": + return MockObj(f"{self.base} *", "") if name == "const": - return MockObj(f'const {self.base}', '') + return MockObj(f"const {self.base}", "") raise ValueError("Expected one of ref, ptr, const.") @property def using(self) -> "MockObj": - assert self.op == '::' - return MockObj(f'using namespace {self.base}') + assert self.op == "::" + return MockObj(f"using namespace {self.base}") def __getitem__(self, item: Union[str, Expression]) -> "MockObj": - next_op = '.' - if isinstance(item, str) and item.startswith('P'): + next_op = "." + if isinstance(item, str) and item.startswith("P"): item = item[1:] - next_op = '->' - return MockObj(f'{self.base}[{item}]', next_op) + next_op = "->" + return MockObj(f"{self.base}[{item}]", next_op) class MockObjEnum(MockObj): def __init__(self, *args, **kwargs): - self._enum = kwargs.pop('enum') - self._is_class = kwargs.pop('is_class') - base = kwargs.pop('base') + self._enum = kwargs.pop("enum") + self._is_class = kwargs.pop("is_class") + base = kwargs.pop("base") if self._is_class: - base = base + '::' + self._enum - kwargs['op'] = '::' - kwargs['base'] = base + base = base + "::" + self._enum + kwargs["op"] = "::" + kwargs["base"] = base MockObj.__init__(self, *args, **kwargs) def __str__(self): if self._is_class: return super().__str__() - return f'{self.base}{self.op}{self._enum}' + return f"{self.base}{self.op}{self._enum}" def __repr__(self): - return f'MockObj<{str(self.base)}>' + return f"MockObj<{str(self.base)}>" class MockObjClass(MockObj): def __init__(self, *args, **kwargs): - parens = kwargs.pop('parents') + parens = kwargs.pop("parents") MockObj.__init__(self, *args, **kwargs) self._parents = [] for paren in parens: @@ -718,7 +790,7 @@ def template(self, *args: SafeExpType) -> "MockObjClass": args = args[0] new_parents = self._parents[:] new_parents.append(self) - return MockObjClass(f'{self.base}{args}', parents=new_parents) + return MockObjClass(f"{self.base}{args}", parents=new_parents) def __repr__(self): - return f'MockObjClass<{str(self.base)}, parents={self._parents}>' + return f"MockObjClass<{str(self.base)}, parents={self._parents}>" diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index f01981acc8f2..e83f989d7a04 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -1,5 +1,12 @@ -from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_SETUP_PRIORITY, \ - CONF_UPDATE_INTERVAL, CONF_TYPE_ID +from esphome.const import ( + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_SETUP_PRIORITY, + CONF_UPDATE_INTERVAL, + CONF_TYPE_ID, +) + # pylint: disable=unused-import from esphome.core import coroutine, ID, CORE, ConfigType from esphome.cpp_generator import RawExpression, add, get_variable @@ -16,6 +23,7 @@ def gpio_pin_expression(conf): if conf is None: return from esphome import pins + for key, (func, _) in pins.PIN_SCHEMA_REGISTRY.items(): if key in conf: yield coroutine(func)(conf) @@ -38,9 +46,11 @@ def register_component(var, config): """ id_ = str(var.base) if id_ not in CORE.component_ids: - raise ValueError("Component ID {} was not declared to inherit from Component, " - "or was registered twice. Please create a bug report with your " - "configuration.".format(id_)) + raise ValueError( + "Component ID {} was not declared to inherit from Component, " + "or was registered twice. Please create a bug report with your " + "configuration.".format(id_) + ) CORE.component_ids.remove(id_) if CONF_SETUP_PRIORITY in config: add(var.set_setup_priority(config[CONF_SETUP_PRIORITY])) diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 4a9dce332b91..3036249a0387 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -1,33 +1,33 @@ from esphome.cpp_generator import MockObj -global_ns = MockObj('', '') -void = global_ns.namespace('void') -nullptr = global_ns.namespace('nullptr') -float_ = global_ns.namespace('float') -double = global_ns.namespace('double') -bool_ = global_ns.namespace('bool') -int_ = global_ns.namespace('int') -std_ns = global_ns.namespace('std') -std_string = std_ns.class_('string') -std_vector = std_ns.class_('vector') -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_ptr = global_ns.namespace('const char *') -NAN = global_ns.namespace('NAN') +global_ns = MockObj("", "") +void = global_ns.namespace("void") +nullptr = global_ns.namespace("nullptr") +float_ = global_ns.namespace("float") +double = global_ns.namespace("double") +bool_ = global_ns.namespace("bool") +int_ = global_ns.namespace("int") +std_ns = global_ns.namespace("std") +std_string = std_ns.class_("string") +std_vector = std_ns.class_("vector") +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_ptr = global_ns.namespace("const char *") +NAN = global_ns.namespace("NAN") esphome_ns = global_ns # using namespace esphome; App = esphome_ns.App -Nameable = esphome_ns.class_('Nameable') -Component = esphome_ns.class_('Component') -ComponentPtr = Component.operator('ptr') -PollingComponent = esphome_ns.class_('PollingComponent', Component) -Application = esphome_ns.class_('Application') -optional = esphome_ns.class_('optional') -arduino_json_ns = global_ns.namespace('ArduinoJson') -JsonObject = arduino_json_ns.class_('JsonObject') -JsonObjectRef = JsonObject.operator('ref') -JsonObjectConstRef = JsonObjectRef.operator('const') -Controller = esphome_ns.class_('Controller') +Nameable = esphome_ns.class_("Nameable") +Component = esphome_ns.class_("Component") +ComponentPtr = Component.operator("ptr") +PollingComponent = esphome_ns.class_("PollingComponent", Component) +Application = esphome_ns.class_("Application") +optional = esphome_ns.class_("optional") +arduino_json_ns = global_ns.namespace("ArduinoJson") +JsonObject = arduino_json_ns.class_("JsonObject") +JsonObjectRef = JsonObject.operator("ref") +JsonObjectConstRef = JsonObjectRef.operator("const") +Controller = esphome_ns.class_("Controller") -GPIOPin = esphome_ns.class_('GPIOPin') +GPIOPin = esphome_ns.class_("GPIOPin") diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index c53c68b3a3ef..1575d9f9b1fd 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -27,8 +27,13 @@ from esphome import const, util from esphome.helpers import mkdir_p, get_bool_env, run_system_command -from esphome.storage_json import EsphomeStorageJSON, StorageJSON, \ - esphome_storage_path, ext_storage_path, trash_storage_path +from esphome.storage_json import ( + EsphomeStorageJSON, + StorageJSON, + esphome_storage_path, + ext_storage_path, + trash_storage_path, +) from esphome.util import shlex_quote, get_serial_ports from .util import password_hash @@ -42,18 +47,18 @@ class DashboardSettings: def __init__(self): - self.config_dir = '' - self.password_hash = '' - self.username = '' + self.config_dir = "" + self.password_hash = "" + self.username = "" self.using_password = False self.on_hassio = False self.cookie_secret = None def parse_args(self, args): self.on_hassio = args.hassio - password = args.password or os.getenv('PASSWORD', '') + password = args.password or os.getenv("PASSWORD", "") if not self.on_hassio: - self.username = args.username or os.getenv('USERNAME', '') + self.username = args.username or os.getenv("USERNAME", "") self.using_password = bool(password) if self.using_password: self.password_hash = password_hash(password) @@ -61,17 +66,17 @@ def parse_args(self, args): @property def relative_url(self): - return os.getenv('ESPHOME_DASHBOARD_RELATIVE_URL', '/') + return os.getenv("ESPHOME_DASHBOARD_RELATIVE_URL", "/") @property def status_use_ping(self): - return get_bool_env('ESPHOME_DASHBOARD_USE_PING') + return get_bool_env("ESPHOME_DASHBOARD_USE_PING") @property def using_hassio_auth(self): if not self.on_hassio: return False - return not get_bool_env('DISABLE_HA_AUTHENTICATION') + return not get_bool_env("DISABLE_HA_AUTHENTICATION") @property def using_auth(self): @@ -84,10 +89,7 @@ def check_password(self, username, password): return False # Compare password in constant running time (to prevent timing attacks) - return hmac.compare_digest( - self.password_hash, - password_hash(password) - ) + return hmac.compare_digest(self.password_hash, password_hash(password)) def rel_path(self, *args): return os.path.join(self.config_dir, *args) @@ -98,24 +100,24 @@ def list_yaml_files(self): settings = DashboardSettings() -cookie_authenticated_yes = b'yes' +cookie_authenticated_yes = b"yes" def template_args(): version = const.__version__ - if 'b' in version: - docs_link = 'https://beta.esphome.io/' - elif 'dev' in version: - docs_link = 'https://next.esphome.io/' + if "b" in version: + docs_link = "https://beta.esphome.io/" + elif "dev" in version: + docs_link = "https://next.esphome.io/" else: - docs_link = 'https://www.esphome.io/' + docs_link = "https://www.esphome.io/" return { - 'version': version, - 'docs_link': docs_link, - 'get_static_file_url': get_static_file_url, - 'relative_url': settings.relative_url, - 'streamer_mode': get_bool_env('ESPHOME_STREAMER_MODE'), - 'config_dir': settings.config_dir, + "version": version, + "docs_link": docs_link, + "get_static_file_url": get_static_file_url, + "relative_url": settings.relative_url, + "streamer_mode": get_bool_env("ESPHOME_STREAMER_MODE"), + "config_dir": settings.config_dir, } @@ -123,9 +125,10 @@ def authenticated(func): @functools.wraps(func) def decorator(self, *args, **kwargs): if not is_authenticated(self): - self.redirect('./login') + self.redirect("./login") return None return func(self, *args, **kwargs) + return decorator @@ -133,23 +136,27 @@ def is_authenticated(request_handler): if settings.on_hassio: # Handle ingress - disable auth on ingress port # X-Hassio-Ingress is automatically stripped on the non-ingress server in nginx - header = request_handler.request.headers.get('X-Hassio-Ingress', 'NO') - if str(header) == 'YES': + header = request_handler.request.headers.get("X-Hassio-Ingress", "NO") + if str(header) == "YES": return True if settings.using_auth: - return request_handler.get_secure_cookie('authenticated') == cookie_authenticated_yes + return ( + request_handler.get_secure_cookie("authenticated") + == cookie_authenticated_yes + ) return True def bind_config(func): def decorator(self, *args, **kwargs): - configuration = self.get_argument('configuration') + configuration = self.get_argument("configuration") if not is_allowed(configuration): self.set_status(500) return None kwargs = kwargs.copy() - kwargs['configuration'] = configuration + kwargs["configuration"] = configuration return func(self, *args, **kwargs) + return decorator @@ -160,7 +167,7 @@ class BaseHandler(tornado.web.RequestHandler): def websocket_class(cls): # pylint: disable=protected-access - if not hasattr(cls, '_message_handlers'): + if not hasattr(cls, "_message_handlers"): cls._message_handlers = {} for _, method in cls.__dict__.items(): @@ -175,6 +182,7 @@ def wrap(fn): # pylint: disable=protected-access fn._message_handler = name return fn + return wrap @@ -190,7 +198,7 @@ def __init__(self, application, request, **kwargs): def on_message(self, message): # Messages are always JSON, 500 when not json_message = json.loads(message) - type_ = json_message['type'] + type_ = json_message["type"] # pylint: disable=no-member handlers = type(self)._message_handlers if type_ not in handlers: @@ -199,17 +207,19 @@ def on_message(self, message): handlers[type_](self, json_message) - @websocket_method('spawn') + @websocket_method("spawn") def handle_spawn(self, json_message): if self._proc is not None: # spawn can only be called once return command = self.build_command(json_message) - _LOGGER.info("Running command '%s'", ' '.join(shlex_quote(x) for x in command)) - self._proc = tornado.process.Subprocess(command, - stdout=tornado.process.Subprocess.STREAM, - stderr=subprocess.STDOUT, - stdin=tornado.process.Subprocess.STREAM) + _LOGGER.info("Running command '%s'", " ".join(shlex_quote(x) for x in command)) + self._proc = tornado.process.Subprocess( + command, + stdout=tornado.process.Subprocess.STREAM, + stderr=subprocess.STDOUT, + stdin=tornado.process.Subprocess.STREAM, + ) self._proc.set_exit_callback(self._proc_on_exit) tornado.ioloop.IOLoop.current().spawn_callback(self._redirect_stdout) @@ -217,34 +227,34 @@ def handle_spawn(self, json_message): def is_process_active(self): return self._proc is not None and self._proc.returncode is None - @websocket_method('stdin') + @websocket_method("stdin") def handle_stdin(self, json_message): if not self.is_process_active: return - data = json_message['data'] - data = codecs.encode(data, 'utf8', 'replace') + data = json_message["data"] + data = codecs.encode(data, "utf8", "replace") _LOGGER.debug("< stdin: %s", data) self._proc.stdin.write(data) @tornado.gen.coroutine def _redirect_stdout(self): - reg = b'[\n\r]' + reg = b"[\n\r]" while True: try: data = yield self._proc.stdout.read_until_regex(reg) except tornado.iostream.StreamClosedError: break - data = codecs.decode(data, 'utf8', 'replace') + data = codecs.decode(data, "utf8", "replace") _LOGGER.debug("> stdout: %s", data) - self.write_message({'event': 'line', 'data': data}) + self.write_message({"event": "line", "data": data}) def _proc_on_exit(self, returncode): if not self._is_closed: # Check if the proc was not forcibly closed _LOGGER.info("Process exited with return code %s", returncode) - self.write_message({'event': 'exit', 'code': returncode}) + self.write_message({"event": "exit", "code": returncode}) def on_close(self): # Check if proc exists (if 'start' has been run) @@ -260,45 +270,57 @@ def build_command(self, json_message): class EsphomeLogsHandler(EsphomeCommandWebSocket): def build_command(self, json_message): - config_file = settings.rel_path(json_message['configuration']) - return ["esphome", "--dashboard", config_file, "logs", '--serial-port', - json_message["port"]] + config_file = settings.rel_path(json_message["configuration"]) + return [ + "esphome", + "--dashboard", + config_file, + "logs", + "--serial-port", + json_message["port"], + ] class EsphomeUploadHandler(EsphomeCommandWebSocket): def build_command(self, json_message): - config_file = settings.rel_path(json_message['configuration']) - return ["esphome", "--dashboard", config_file, "run", '--upload-port', - json_message["port"]] + config_file = settings.rel_path(json_message["configuration"]) + return [ + "esphome", + "--dashboard", + config_file, + "run", + "--upload-port", + json_message["port"], + ] class EsphomeCompileHandler(EsphomeCommandWebSocket): def build_command(self, json_message): - config_file = settings.rel_path(json_message['configuration']) + config_file = settings.rel_path(json_message["configuration"]) return ["esphome", "--dashboard", config_file, "compile"] class EsphomeValidateHandler(EsphomeCommandWebSocket): def build_command(self, json_message): - config_file = settings.rel_path(json_message['configuration']) + config_file = settings.rel_path(json_message["configuration"]) return ["esphome", "--dashboard", config_file, "config"] class EsphomeCleanMqttHandler(EsphomeCommandWebSocket): def build_command(self, json_message): - config_file = settings.rel_path(json_message['configuration']) + config_file = settings.rel_path(json_message["configuration"]) return ["esphome", "--dashboard", config_file, "clean-mqtt"] class EsphomeCleanHandler(EsphomeCommandWebSocket): def build_command(self, json_message): - config_file = settings.rel_path(json_message['configuration']) + config_file = settings.rel_path(json_message["configuration"]) return ["esphome", "--dashboard", config_file, "clean"] class EsphomeVscodeHandler(EsphomeCommandWebSocket): def build_command(self, json_message): - return ["esphome", "--dashboard", "-q", 'dummy', "vscode"] + return ["esphome", "--dashboard", "-q", "dummy", "vscode"] class EsphomeAceEditorHandler(EsphomeCommandWebSocket): @@ -318,15 +340,15 @@ def get(self): data = [] for port in ports: desc = port.description - if port.path == '/dev/ttyAMA0': - desc = 'UART pins on GPIO header' - split_desc = desc.split(' - ') + if port.path == "/dev/ttyAMA0": + desc = "UART pins on GPIO header" + split_desc = desc.split(" - ") if len(split_desc) == 2 and split_desc[0] == split_desc[1]: # Some serial ports repeat their values desc = split_desc[0] - data.append({'port': port.path, 'desc': desc}) - data.append({'port': 'OTA', 'desc': 'Over-The-Air'}) - data.sort(key=lambda x: x['port'], reverse=True) + data.append({"port": port.path, "desc": desc}) + data.append({"port": "OTA", "desc": "Over-The-Air"}) + data.sort(key=lambda x: x["port"], reverse=True) self.write(json.dumps(data)) @@ -336,12 +358,11 @@ def post(self): from esphome import wizard kwargs = { - k: ''.join(x.decode() for x in v) - for k, v in self.request.arguments.items() + k: "".join(x.decode() for x in v) for k, v in self.request.arguments.items() } - destination = settings.rel_path(kwargs['name'] + '.yaml') + destination = settings.rel_path(kwargs["name"] + ".yaml") wizard.wizard_write(path=destination, **kwargs) - self.redirect('./?begin=True') + self.redirect("./?begin=True") class DownloadBinaryRequestHandler(BaseHandler): @@ -356,10 +377,10 @@ def get(self, configuration=None): return path = storage_json.firmware_bin_path - self.set_header('Content-Type', 'application/octet-stream') - filename = f'{storage_json.name}.bin' + self.set_header("Content-Type", "application/octet-stream") + filename = f"{storage_json.name}.bin" self.set_header("Content-Disposition", f'attachment; filename="{filename}"') - with open(path, 'rb') as f: + with open(path, "rb") as f: while True: data = f.read(16384) if not data: @@ -386,7 +407,9 @@ def filename(self): @property def storage(self): # type: () -> Optional[StorageJSON] if not self._loaded_storage: - self._storage = StorageJSON.load(ext_storage_path(settings.config_dir, self.filename)) + self._storage = StorageJSON.load( + ext_storage_path(settings.config_dir, self.filename) + ) self._loaded_storage = True return self._storage @@ -399,7 +422,7 @@ def address(self): @property def name(self): if self.storage is None: - return self.filename[:-len('.yaml')] + return self.filename[: -len(".yaml")] return self.storage.name @property @@ -429,8 +452,8 @@ def update_available(self): @property def update_old(self): if self.storage is None: - return '' - return self.storage.esphome_version or '' + return "" + return self.storage.esphome_version or "" @property def update_new(self): @@ -446,18 +469,23 @@ def loaded_integrations(self): class MainRequestHandler(BaseHandler): @authenticated def get(self): - begin = bool(self.get_argument('begin', False)) + begin = bool(self.get_argument("begin", False)) entries = _list_dashboard_entries() - self.render("templates/index.html", entries=entries, begin=begin, - **template_args(), login_enabled=settings.using_auth) + self.render( + "templates/index.html", + entries=entries, + begin=begin, + **template_args(), + login_enabled=settings.using_auth, + ) def _ping_func(filename, address): - if os.name == 'nt': - command = ['ping', '-n', '1', address] + if os.name == "nt": + command = ["ping", "-n", "1", address] else: - command = ['ping', '-c', '1', address] + command = ["ping", "-c", "1", address] rc, _, _ = run_system_command(*command) return filename, rc == 0 @@ -474,7 +502,9 @@ def on_update(dat): stat.start() while not STOP_EVENT.is_set(): entries = _list_dashboard_entries() - stat.request_query({entry.filename: entry.name + '.local.' for entry in entries}) + stat.request_query( + {entry.filename: entry.name + ".local." for entry in entries} + ) PING_REQUEST.wait() PING_REQUEST.clear() @@ -499,8 +529,9 @@ def callback(ret): PING_RESULT[entry.filename] = None continue - result = pool.apply_async(_ping_func, (entry.filename, entry.address), - callback=callback) + result = pool.apply_async( + _ping_func, (entry.filename, entry.address), callback=callback + ) queue.append(result) while queue: @@ -541,10 +572,10 @@ class EditRequestHandler(BaseHandler): @bind_config def get(self, configuration=None): filename = settings.rel_path(configuration) - content = '' + content = "" if os.path.isfile(filename): # pylint: disable=no-value-for-parameter - with open(filename, 'r') as f: + with open(filename, "r") as f: content = f.read() self.write(content) @@ -552,7 +583,7 @@ def get(self, configuration=None): @bind_config def post(self, configuration=None): # pylint: disable=no-value-for-parameter - with open(settings.rel_path(configuration), 'wb') as f: + with open(settings.rel_path(configuration), "wb") as f: f.write(self.request.body) self.set_status(200) @@ -596,29 +627,34 @@ def post(self, configuration=None): class LoginHandler(BaseHandler): def get(self): if is_authenticated(self): - self.redirect('/') + self.redirect("/") else: self.render_login_page() def render_login_page(self, error=None): - self.render("templates/login.html", error=error, hassio=settings.using_hassio_auth, - has_username=bool(settings.username), **template_args()) + self.render( + "templates/login.html", + error=error, + hassio=settings.using_hassio_auth, + has_username=bool(settings.username), + **template_args(), + ) def post_hassio_login(self): import requests headers = { - 'X-HASSIO-KEY': os.getenv('HASSIO_TOKEN'), + "X-HASSIO-KEY": os.getenv("HASSIO_TOKEN"), } data = { - 'username': self.get_argument('username', ''), - 'password': self.get_argument('password', '') + "username": self.get_argument("username", ""), + "password": self.get_argument("password", ""), } try: - req = requests.post('http://hassio/auth', headers=headers, data=data) + req = requests.post("http://hassio/auth", headers=headers, data=data) if req.status_code == 200: self.set_secure_cookie("authenticated", cookie_authenticated_yes) - self.redirect('/') + self.redirect("/") return except Exception as err: # pylint: disable=broad-except _LOGGER.warning("Error during Hass.io auth request: %s", err) @@ -629,13 +665,15 @@ def post_hassio_login(self): self.render_login_page(error="Invalid username or password") def post_native_login(self): - username = self.get_argument("username", '') - password = self.get_argument("password", '') + username = self.get_argument("username", "") + password = self.get_argument("password", "") if settings.check_password(username, password): self.set_secure_cookie("authenticated", cookie_authenticated_yes) self.redirect("/") return - error_str = "Invalid username or password" if settings.username else "Invalid password" + error_str = ( + "Invalid username or password" if settings.username else "Invalid password" + ) self.set_status(401) self.render_login_page(error=error_str) @@ -650,22 +688,22 @@ class LogoutHandler(BaseHandler): @authenticated def get(self): self.clear_cookie("authenticated") - self.redirect('./login') + self.redirect("./login") _STATIC_FILE_HASHES = {} def get_static_file_url(name): - static_path = os.path.join(os.path.dirname(__file__), 'static') + static_path = os.path.join(os.path.dirname(__file__), "static") if name in _STATIC_FILE_HASHES: hash_ = _STATIC_FILE_HASHES[name] else: path = os.path.join(static_path, name) - with open(path, 'rb') as f_handle: + with open(path, "rb") as f_handle: hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] _STATIC_FILE_HASHES[name] = hash_ - return f'./static/{name}?hash={hash_}' + return f"./static/{name}?hash={hash_}" def make_app(debug=False): @@ -684,44 +722,53 @@ def log_function(handler): request_time = 1000.0 * handler.request.request_time() # pylint: disable=protected-access - log_method("%d %s %.2fms", handler.get_status(), - handler._request_summary(), request_time) + log_method( + "%d %s %.2fms", + handler.get_status(), + handler._request_summary(), + request_time, + ) class StaticFileHandler(tornado.web.StaticFileHandler): def set_extra_headers(self, path): if debug: - self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + self.set_header( + "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" + ) - static_path = os.path.join(os.path.dirname(__file__), 'static') + static_path = os.path.join(os.path.dirname(__file__), "static") app_settings = { - 'debug': debug, - 'cookie_secret': settings.cookie_secret, - 'log_function': log_function, - 'websocket_ping_interval': 30.0, + "debug": debug, + "cookie_secret": settings.cookie_secret, + "log_function": log_function, + "websocket_ping_interval": 30.0, } rel = settings.relative_url - app = tornado.web.Application([ - (rel + "", MainRequestHandler), - (rel + "login", LoginHandler), - (rel + "logout", LogoutHandler), - (rel + "logs", EsphomeLogsHandler), - (rel + "upload", EsphomeUploadHandler), - (rel + "compile", EsphomeCompileHandler), - (rel + "validate", EsphomeValidateHandler), - (rel + "clean-mqtt", EsphomeCleanMqttHandler), - (rel + "clean", EsphomeCleanHandler), - (rel + "vscode", EsphomeVscodeHandler), - (rel + "ace", EsphomeAceEditorHandler), - (rel + "update-all", EsphomeUpdateAllHandler), - (rel + "edit", EditRequestHandler), - (rel + "download.bin", DownloadBinaryRequestHandler), - (rel + "serial-ports", SerialPortRequestHandler), - (rel + "ping", PingRequestHandler), - (rel + "delete", DeleteRequestHandler), - (rel + "undo-delete", UndoDeleteRequestHandler), - (rel + "wizard.html", WizardRequestHandler), - (rel + r"static/(.*)", StaticFileHandler, {'path': static_path}), - ], **app_settings) + app = tornado.web.Application( + [ + (rel + "", MainRequestHandler), + (rel + "login", LoginHandler), + (rel + "logout", LogoutHandler), + (rel + "logs", EsphomeLogsHandler), + (rel + "upload", EsphomeUploadHandler), + (rel + "compile", EsphomeCompileHandler), + (rel + "validate", EsphomeValidateHandler), + (rel + "clean-mqtt", EsphomeCleanMqttHandler), + (rel + "clean", EsphomeCleanHandler), + (rel + "vscode", EsphomeVscodeHandler), + (rel + "ace", EsphomeAceEditorHandler), + (rel + "update-all", EsphomeUpdateAllHandler), + (rel + "edit", EditRequestHandler), + (rel + "download.bin", DownloadBinaryRequestHandler), + (rel + "serial-ports", SerialPortRequestHandler), + (rel + "ping", PingRequestHandler), + (rel + "delete", DeleteRequestHandler), + (rel + "undo-delete", UndoDeleteRequestHandler), + (rel + "wizard.html", WizardRequestHandler), + (rel + r"static/(.*)", StaticFileHandler, {"path": static_path}), + ], + **app_settings, + ) if debug: _STATIC_FILE_HASHES.clear() @@ -743,20 +790,26 @@ def start_web_server(args): app = make_app(args.verbose) if args.socket is not None: - _LOGGER.info("Starting dashboard web server on unix socket %s and configuration dir %s...", - args.socket, settings.config_dir) + _LOGGER.info( + "Starting dashboard web server on unix socket %s and configuration dir %s...", + args.socket, + settings.config_dir, + ) server = tornado.httpserver.HTTPServer(app) socket = tornado.netutil.bind_unix_socket(args.socket, mode=0o666) server.add_socket(socket) else: - _LOGGER.info("Starting dashboard web server on port %s and configuration dir %s...", - args.port, settings.config_dir) + _LOGGER.info( + "Starting dashboard web server on port %s and configuration dir %s...", + args.port, + settings.config_dir, + ) app.listen(args.port) if args.open_ui: import webbrowser - webbrowser.open(f'localhost:{args.port}') + webbrowser.open(f"localhost:{args.port}") if settings.status_use_ping: status_thread = PingStatusThread() diff --git a/esphome/espota2.py b/esphome/espota2.py index a1408a7d446d..785cb155df38 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -52,14 +52,15 @@ def update(self, progress): return self.last_progress = new_progress block = int(round(bar_length * progress)) - text = "\rUploading: [{0}] {1}% {2}".format("=" * block + " " * (bar_length - block), - new_progress, status) + text = "\rUploading: [{0}] {1}% {2}".format( + "=" * block + " " * (bar_length - block), new_progress, status + ) sys.stderr.write(text) sys.stderr.flush() # pylint: disable=no-self-use def done(self): - sys.stderr.write('\n') + sys.stderr.write("\n") sys.stderr.flush() @@ -78,7 +79,7 @@ def receive_exactly(sock, amount, msg, expect, decode=True): if decode: data = [] else: - data = b'' + data = b"" try: data += recv_decode(sock, 1, decode=decode) @@ -106,32 +107,48 @@ def check_error(data, expect): if dat == RESPONSE_ERROR_MAGIC: raise OTAError("Error: Invalid magic byte") if dat == RESPONSE_ERROR_UPDATE_PREPARE: - raise OTAError("Error: Couldn't prepare flash memory for update. Is the binary too big? " - "Please try restarting the ESP.") + raise OTAError( + "Error: Couldn't prepare flash memory for update. Is the binary too big? " + "Please try restarting the ESP." + ) if dat == RESPONSE_ERROR_AUTH_INVALID: raise OTAError("Error: Authentication invalid. Is the password correct?") if dat == RESPONSE_ERROR_WRITING_FLASH: - raise OTAError("Error: Wring OTA data to flash memory failed. See USB logs for more " - "information.") + raise OTAError( + "Error: Wring OTA data to flash memory failed. See USB logs for more " + "information." + ) if dat == RESPONSE_ERROR_UPDATE_END: - raise OTAError("Error: Finishing update failed. See the MQTT/USB logs for more " - "information.") + raise OTAError( + "Error: Finishing update failed. See the MQTT/USB logs for more " + "information." + ) if dat == RESPONSE_ERROR_INVALID_BOOTSTRAPPING: - raise OTAError("Error: Please press the reset button on the ESP. A manual reset is " - "required on the first OTA-Update after flashing via USB.") + raise OTAError( + "Error: Please press the reset button on the ESP. A manual reset is " + "required on the first OTA-Update after flashing via USB." + ) if dat == RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG: - raise OTAError("Error: ESP has been flashed with wrong flash size. Please choose the " - "correct 'board' option (esp01_1m always works) and then flash over USB.") + raise OTAError( + "Error: ESP has been flashed with wrong flash size. Please choose the " + "correct 'board' option (esp01_1m always works) and then flash over USB." + ) if dat == RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG: - raise OTAError("Error: ESP does not have the requested flash size (wrong board). Please " - "choose the correct 'board' option (esp01_1m always works) and try " - "uploading again.") + raise OTAError( + "Error: ESP does not have the requested flash size (wrong board). Please " + "choose the correct 'board' option (esp01_1m always works) and try " + "uploading again." + ) if dat == RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE: - raise OTAError("Error: ESP does not have enough space to store OTA file. Please try " - "flashing a minimal firmware (remove everything except ota)") + raise OTAError( + "Error: ESP does not have enough space to store OTA file. Please try " + "flashing a minimal firmware (remove everything except ota)" + ) if dat == RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE: - raise OTAError("Error: The OTA partition on the ESP is too small. ESPHome needs to resize " - "this partition, please flash over USB.") + raise OTAError( + "Error: The OTA partition on the ESP is too small. ESPHome needs to resize " + "this partition, please flash over USB." + ) if dat == RESPONSE_ERROR_UNKNOWN: raise OTAError("Unknown error from ESP") if not isinstance(expect, (list, tuple)): @@ -147,7 +164,7 @@ def send_check(sock, data, msg): elif isinstance(data, int): data = bytes([data]) elif isinstance(data, str): - data = data.encode('utf8') + data = data.encode("utf8") sock.sendall(data) except OSError as err: @@ -157,42 +174,46 @@ def send_check(sock, data, msg): def perform_ota(sock, password, file_handle, filename): file_md5 = hashlib.md5(file_handle.read()).hexdigest() file_size = file_handle.tell() - _LOGGER.info('Uploading %s (%s bytes)', filename, file_size) + _LOGGER.info("Uploading %s (%s bytes)", filename, file_size) file_handle.seek(0) _LOGGER.debug("MD5 of binary is %s", file_md5) # Enable nodelay, we need it for phase 1 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - send_check(sock, MAGIC_BYTES, 'magic bytes') + send_check(sock, MAGIC_BYTES, "magic bytes") - _, version = receive_exactly(sock, 2, 'version', RESPONSE_OK) + _, version = receive_exactly(sock, 2, "version", RESPONSE_OK) if version != OTA_VERSION_1_0: raise OTAError(f"Unsupported OTA version {version}") # Features - send_check(sock, 0x00, 'features') - receive_exactly(sock, 1, 'features', RESPONSE_HEADER_OK) + send_check(sock, 0x00, "features") + receive_exactly(sock, 1, "features", RESPONSE_HEADER_OK) - auth, = receive_exactly(sock, 1, 'auth', [RESPONSE_REQUEST_AUTH, RESPONSE_AUTH_OK]) + (auth,) = receive_exactly( + sock, 1, "auth", [RESPONSE_REQUEST_AUTH, RESPONSE_AUTH_OK] + ) if auth == RESPONSE_REQUEST_AUTH: if not password: raise OTAError("ESP requests password, but no password given!") - nonce = receive_exactly(sock, 32, 'authentication nonce', [], decode=False).decode() + nonce = receive_exactly( + sock, 32, "authentication nonce", [], decode=False + ).decode() _LOGGER.debug("Auth: Nonce is %s", nonce) cnonce = hashlib.md5(str(random.random()).encode()).hexdigest() _LOGGER.debug("Auth: CNonce is %s", cnonce) - send_check(sock, cnonce, 'auth cnonce') + send_check(sock, cnonce, "auth cnonce") result_md5 = hashlib.md5() - result_md5.update(password.encode('utf-8')) + result_md5.update(password.encode("utf-8")) result_md5.update(nonce.encode()) result_md5.update(cnonce.encode()) result = result_md5.hexdigest() _LOGGER.debug("Auth: Result is %s", result) - send_check(sock, result, 'auth result') - receive_exactly(sock, 1, 'auth result', RESPONSE_AUTH_OK) + send_check(sock, result, "auth result") + receive_exactly(sock, 1, "auth result", RESPONSE_AUTH_OK) file_size_encoded = [ (file_size >> 24) & 0xFF, @@ -200,11 +221,11 @@ def perform_ota(sock, password, file_handle, filename): (file_size >> 8) & 0xFF, (file_size >> 0) & 0xFF, ] - send_check(sock, file_size_encoded, 'binary size') - receive_exactly(sock, 1, 'binary size', RESPONSE_UPDATE_PREPARE_OK) + send_check(sock, file_size_encoded, "binary size") + receive_exactly(sock, 1, "binary size", RESPONSE_UPDATE_PREPARE_OK) - send_check(sock, file_md5, 'file checksum') - receive_exactly(sock, 1, 'file checksum', RESPONSE_BIN_MD5_OK) + send_check(sock, file_md5, "file checksum") + receive_exactly(sock, 1, "file checksum", RESPONSE_BIN_MD5_OK) # Disable nodelay for transfer sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 0) @@ -225,7 +246,7 @@ def perform_ota(sock, password, file_handle, filename): try: sock.sendall(chunk) except OSError as err: - sys.stderr.write('\n') + sys.stderr.write("\n") raise OTAError(f"Error sending data: {err}") from err progress.update(offset / float(file_size)) @@ -236,9 +257,9 @@ def perform_ota(sock, password, file_handle, filename): _LOGGER.info("Waiting for result...") - receive_exactly(sock, 1, 'receive OK', RESPONSE_RECEIVE_OK) - receive_exactly(sock, 1, 'Update end', RESPONSE_UPDATE_END_OK) - send_check(sock, RESPONSE_OK, 'end acknowledgement') + receive_exactly(sock, 1, "receive OK", RESPONSE_RECEIVE_OK) + receive_exactly(sock, 1, "Update end", RESPONSE_UPDATE_END_OK) + send_check(sock, RESPONSE_OK, "end acknowledgement") _LOGGER.info("OTA successful") @@ -255,10 +276,14 @@ def run_ota_impl_(remote_host, remote_port, password, filename): try: ip = resolve_ip_address(remote_host) except EsphomeError as err: - _LOGGER.error("Error resolving IP address of %s. Is it connected to WiFi?", - remote_host) - _LOGGER.error("(If this error persists, please set a static IP address: " - "https://esphome.io/components/wifi.html#manual-ips)") + _LOGGER.error( + "Error resolving IP address of %s. Is it connected to WiFi?", + remote_host, + ) + _LOGGER.error( + "(If this error persists, please set a static IP address: " + "https://esphome.io/components/wifi.html#manual-ips)" + ) raise OTAError(err) from err _LOGGER.info(" -> %s", ip) @@ -271,7 +296,7 @@ def run_ota_impl_(remote_host, remote_port, password, filename): _LOGGER.error("Connecting to %s:%s failed: %s", remote_host, remote_port, err) return 1 - file_handle = open(filename, 'rb') + file_handle = open(filename, "rb") try: perform_ota(sock, password, file_handle, filename) except OTAError as err: diff --git a/esphome/helpers.py b/esphome/helpers.py index 1389804fd96a..780a2aa88e23 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -22,48 +22,48 @@ def ensure_unique_string(preferred_string, current_strings): return test_string -def indent_all_but_first_and_last(text, padding=' '): +def indent_all_but_first_and_last(text, padding=" "): lines = text.splitlines(True) if len(lines) <= 2: return text - return lines[0] + ''.join(padding + line for line in lines[1:-1]) + lines[-1] + return lines[0] + "".join(padding + line for line in lines[1:-1]) + lines[-1] -def indent_list(text, padding=' '): +def indent_list(text, padding=" "): return [padding + line for line in text.splitlines()] -def indent(text, padding=' '): - return '\n'.join(indent_list(text, padding)) +def indent(text, padding=" "): + return "\n".join(indent_list(text, padding)) # From https://stackoverflow.com/a/14945195/8924614 -def cpp_string_escape(string, encoding='utf-8'): +def cpp_string_escape(string, encoding="utf-8"): def _should_escape(byte): # type: (int) -> bool if not 32 <= byte < 127: return True - if byte in (ord('\\'), ord('"')): + if byte in (ord("\\"), ord('"')): return True return False if isinstance(string, str): string = string.encode(encoding) - result = '' + result = "" for character in string: if _should_escape(character): - result += f'\\{character:03o}' + result += f"\\{character:03o}" else: result += chr(character) return '"' + result + '"' -def color(the_color, message=''): +def color(the_color, message=""): from colorlog.escape_codes import escape_codes, parse_colors if not message: res = parse_colors(the_color) else: - res = parse_colors(the_color) + message + escape_codes['reset'] + res = parse_colors(the_color) + message + escape_codes["reset"] return res @@ -85,15 +85,17 @@ def mkdir_p(path): os.makedirs(path) except OSError as err: import errno + if err.errno == errno.EEXIST and os.path.isdir(path): pass else: from esphome.core import EsphomeError + raise EsphomeError(f"Error creating directories {path}: {err}") from err def is_ip_address(host): - parts = host.split('.') + parts = host.split(".") if len(parts) != 4: return False try: @@ -111,17 +113,21 @@ def _resolve_with_zeroconf(host): try: zc = Zeroconf() except Exception as err: - raise EsphomeError("Cannot start mDNS sockets, is this a docker container without " - "host network mode?") from err + raise EsphomeError( + "Cannot start mDNS sockets, is this a docker container without " + "host network mode?" + ) from err try: - info = zc.resolve_host(host + '.') + info = zc.resolve_host(host + ".") except Exception as err: raise EsphomeError(f"Error resolving mDNS hostname: {err}") from err finally: zc.close() if info is None: - raise EsphomeError("Error resolving address with mDNS: Did not respond. " - "Maybe the device is offline.") + raise EsphomeError( + "Error resolving address with mDNS: Did not respond. " + "Maybe the device is offline." + ) return info @@ -131,7 +137,7 @@ def resolve_ip_address(host): errs = [] - if host.endswith('.local'): + if host.endswith(".local"): try: return _resolve_with_zeroconf(host) except EsphomeError as err: @@ -141,8 +147,9 @@ def resolve_ip_address(host): return socket.gethostbyname(host) except OSError as err: errs.append(str(err)) - raise EsphomeError("Error resolving IP address: {}" - "".format(', '.join(errs))) from err + raise EsphomeError( + "Error resolving IP address: {}" "".format(", ".join(errs)) + ) from err def get_bool_env(var, default=False): @@ -150,7 +157,7 @@ def get_bool_env(var, default=False): def is_hassio(): - return get_bool_env('ESPHOME_IS_HASSIO') + return get_bool_env("ESPHOME_IS_HASSIO") def walk_files(path): @@ -161,13 +168,15 @@ def walk_files(path): def read_file(path): try: - with codecs.open(path, 'r', encoding='utf-8') as f_handle: + with codecs.open(path, "r", encoding="utf-8") as f_handle: return f_handle.read() except OSError as err: from esphome.core import EsphomeError + raise EsphomeError(f"Error reading file {path}: {err}") from err except UnicodeDecodeError as err: from esphome.core import EsphomeError + raise EsphomeError(f"Error reading file {path}: {err}") from err @@ -187,7 +196,9 @@ def _write_file(path: Union[Path, str], text: Union[str, bytes]): tmp_path = None try: - with tempfile.NamedTemporaryFile(mode="wb", dir=directory, delete=False) as f_handle: + with tempfile.NamedTemporaryFile( + mode="wb", dir=directory, delete=False + ) as f_handle: tmp_path = f_handle.name f_handle.write(data) # Newer tempfile implementations create the file with mode 0o600 @@ -207,6 +218,7 @@ def write_file(path: Union[Path, str], text: str): _write_file(path, text) except OSError as err: from esphome.core import EsphomeError + raise EsphomeError(f"Could not write file at {path}") from err @@ -223,6 +235,7 @@ def write_file_if_changed(path: Union[Path, str], text: str): def copy_file_if_changed(src, dst): import shutil + if file_compare(src, dst): return mkdir_p(os.path.dirname(dst)) @@ -230,6 +243,7 @@ def copy_file_if_changed(src, dst): shutil.copy(src, dst) except OSError as err: from esphome.core import EsphomeError + raise EsphomeError(f"Error copying file {src} to {dst}: {err}") from err @@ -247,16 +261,19 @@ def file_compare(path1, path2): # File doesn't exist or another error -> not equal return False - if stat.S_IFMT(stat1.st_mode) != stat.S_IFREG or stat.S_IFMT(stat2.st_mode) != stat.S_IFREG: + if ( + stat.S_IFMT(stat1.st_mode) != stat.S_IFREG + or stat.S_IFMT(stat2.st_mode) != stat.S_IFREG + ): # At least one of them is not a regular file (or does not exist) return False if stat1.st_size != stat2.st_size: # Different sizes return False - bufsize = 8*1024 + bufsize = 8 * 1024 # Read files in blocks until a mismatch is found - with open(path1, 'rb') as fh1, open(path2, 'rb') as fh2: + with open(path1, "rb") as fh1, open(path2, "rb") as fh2: while True: blob1, blob2 = fh1.read(bufsize), fh2.read(bufsize) if blob1 != blob2: @@ -270,11 +287,11 @@ def file_compare(path1, path2): # A dict of types that need to be converted to heaptypes before a class can be added # to the object _TYPE_OVERLOADS = { - int: type('EInt', (int,), dict()), - float: type('EFloat', (float,), dict()), - str: type('EStr', (str,), dict()), - dict: type('EDict', (str,), dict()), - list: type('EList', (list,), dict()), + int: type("EInt", (int,), dict()), + float: type("EFloat", (float,), dict()), + str: type("EStr", (str,), dict()), + dict: type("EDict", (str,), dict()), + list: type("EList", (list,), dict()), } # cache created classes here diff --git a/esphome/jsonschema.py b/esphome/jsonschema.py new file mode 100644 index 000000000000..12929dc6029f --- /dev/null +++ b/esphome/jsonschema.py @@ -0,0 +1,91 @@ +"""Helpers to retrieve schema from voluptuous validators. + +These are a helper decorators to help get schema from some +components which uses volutuous in a way where validation +is hidden in local functions +These decorators should not modify at all what the functions +originally do. +However there is a property to further disable decorator +impact.""" + + +# This is set to true by script/build_jsonschema.py +# only, so data is collected (again functionality is not modified) +EnableJsonSchemaCollect = False + +extended_schemas = {} +list_schemas = {} +registry_schemas = {} +hidden_schemas = {} +typed_schemas = {} + + +def jschema_extractor(validator_name): + if EnableJsonSchemaCollect: + + def decorator(func): + hidden_schemas[str(func)] = validator_name + return func + + return decorator + + def dummy(f): + return f + + return dummy + + +def jschema_extended(func): + if EnableJsonSchemaCollect: + + def decorate(*args, **kwargs): + ret = func(*args, **kwargs) + assert len(args) == 2 + extended_schemas[str(ret)] = args + return ret + + return decorate + + return func + + +def jschema_composite(func): + if EnableJsonSchemaCollect: + + def decorate(*args, **kwargs): + ret = func(*args, **kwargs) + # args length might be 2, but 2nd is always validator + list_schemas[str(ret)] = args + return ret + + return decorate + + return func + + +def jschema_registry(registry): + if EnableJsonSchemaCollect: + + def decorator(func): + registry_schemas[str(func)] = registry + return func + + return decorator + + def dummy(f): + return f + + return dummy + + +def jschema_typed(func): + if EnableJsonSchemaCollect: + + def decorate(*args, **kwargs): + ret = func(*args, **kwargs) + typed_schemas[str(ret)] = (args, kwargs) + return ret + + return decorate + + return func diff --git a/esphome/legacy.py b/esphome/legacy.py index 27373ee1a3b1..6b3b1d6c99a2 100644 --- a/esphome/legacy.py +++ b/esphome/legacy.py @@ -4,7 +4,7 @@ def main(): print("The esphomeyaml command has been renamed to esphome.") print("") - print("$ esphome {}".format(' '.join(sys.argv[1:]))) + print("$ esphome {}".format(" ".join(sys.argv[1:]))) return 1 diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 499ccbe7f15e..86937ba37e75 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -7,9 +7,20 @@ import paho.mqtt.client as mqtt -from esphome.const import CONF_BROKER, CONF_DISCOVERY_PREFIX, CONF_ESPHOME, \ - CONF_LOG_TOPIC, CONF_MQTT, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SSL_FINGERPRINTS, \ - CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_USERNAME +from esphome.const import ( + CONF_BROKER, + CONF_DISCOVERY_PREFIX, + CONF_ESPHOME, + CONF_LOG_TOPIC, + CONF_MQTT, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_SSL_FINGERPRINTS, + CONF_TOPIC, + CONF_TOPIC_PREFIX, + CONF_USERNAME, +) from esphome.core import CORE, EsphomeError from esphome.helpers import color from esphome.util import safe_print @@ -36,21 +47,24 @@ def on_disconnect(client, userdata, result_code): except OSError: pass - wait_time = min(2**tries, 300) + wait_time = min(2 ** tries, 300) _LOGGER.warning( "Disconnected from MQTT (%s). Trying to reconnect in %s s", - result_code, wait_time) + result_code, + wait_time, + ) time.sleep(wait_time) tries += 1 - client = mqtt.Client(client_id or '') + client = mqtt.Client(client_id or "") client.on_connect = on_connect client.on_message = on_message client.on_disconnect = on_disconnect if username is None: if config[CONF_MQTT].get(CONF_USERNAME): - client.username_pw_set(config[CONF_MQTT][CONF_USERNAME], - config[CONF_MQTT][CONF_PASSWORD]) + client.username_pw_set( + config[CONF_MQTT][CONF_USERNAME], config[CONF_MQTT][CONF_PASSWORD] + ) elif username: client.username_pw_set(username, password) @@ -59,8 +73,14 @@ def on_disconnect(client, userdata, result_code): tls_version = ssl.PROTOCOL_TLS # pylint: disable=no-member else: tls_version = ssl.PROTOCOL_SSLv23 - client.tls_set(ca_certs=None, certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED, - tls_version=tls_version, ciphers=None) + client.tls_set( + ca_certs=None, + certfile=None, + keyfile=None, + cert_reqs=ssl.CERT_REQUIRED, + tls_version=tls_version, + ciphers=None, + ) try: host = str(config[CONF_MQTT][CONF_BROKER]) @@ -84,17 +104,17 @@ def show_logs(config, topic=None, username=None, password=None, client_id=None): if CONF_LOG_TOPIC in conf: topic = config[CONF_MQTT][CONF_LOG_TOPIC][CONF_TOPIC] elif CONF_TOPIC_PREFIX in config[CONF_MQTT]: - topic = config[CONF_MQTT][CONF_TOPIC_PREFIX] + '/debug' + topic = config[CONF_MQTT][CONF_TOPIC_PREFIX] + "/debug" else: - topic = config[CONF_ESPHOME][CONF_NAME] + '/debug' + topic = config[CONF_ESPHOME][CONF_NAME] + "/debug" else: _LOGGER.error("MQTT isn't setup, can't start MQTT logs") return 1 _LOGGER.info("Starting log output from %s", topic) def on_message(client, userdata, msg): - time_ = datetime.now().time().strftime('[%H:%M:%S]') - payload = msg.payload.decode(errors='backslashreplace') + time_ = datetime.now().time().strftime("[%H:%M:%S]") + payload = msg.payload.decode(errors="backslashreplace") message = time_ + payload safe_print(message) @@ -103,12 +123,14 @@ def on_message(client, userdata, msg): def clear_topic(config, topic, username=None, password=None, client_id=None): if topic is None: - discovery_prefix = config[CONF_MQTT].get(CONF_DISCOVERY_PREFIX, 'homeassistant') + discovery_prefix = config[CONF_MQTT].get(CONF_DISCOVERY_PREFIX, "homeassistant") name = config[CONF_ESPHOME][CONF_NAME] - topic = f'{discovery_prefix}/+/{name}/#' + topic = f"{discovery_prefix}/+/{name}/#" _LOGGER.info("Clearing messages from '%s'", topic) - _LOGGER.info("Please close this window when no more messages appear and the " - "MQTT topic has been cleared of retained messages.") + _LOGGER.info( + "Please close this window when no more messages appear and the " + "MQTT topic has been cleared of retained messages." + ) def on_message(client, userdata, msg): if not msg.payload or not msg.retain: @@ -136,7 +158,9 @@ def get_fingerprint(config): sha1 = hashlib.sha1(cert_der).hexdigest() - safe_print("SHA1 Fingerprint: " + color('cyan', sha1)) - safe_print("Copy the string above into mqtt.ssl_fingerprints section of {}" - "".format(CORE.config_path)) + safe_print("SHA1 Fingerprint: " + color("cyan", sha1)) + safe_print( + "Copy the string above into mqtt.ssl_fingerprints section of {}" + "".format(CORE.config_path) + ) return 0 diff --git a/esphome/pins.py b/esphome/pins.py index b6fde852509c..fef77f3946a3 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -8,53 +8,149 @@ _LOGGER = logging.getLogger(__name__) ESP8266_BASE_PINS = { - 'A0': 17, 'SS': 15, 'MOSI': 13, 'MISO': 12, 'SCK': 14, 'SDA': 4, 'SCL': 5, 'RX': 3, 'TX': 1 + "A0": 17, + "SS": 15, + "MOSI": 13, + "MISO": 12, + "SCK": 14, + "SDA": 4, + "SCL": 5, + "RX": 3, + "TX": 1, } ESP8266_BOARD_PINS = { - 'd1': {'D0': 3, 'D1': 1, 'D2': 16, 'D3': 5, 'D4': 4, 'D5': 14, 'D6': 12, 'D7': 13, 'D8': 0, - 'D9': 2, 'D10': 15, 'D11': 13, 'D12': 14, 'D13': 14, 'D14': 4, 'D15': 5, 'LED': 2}, - 'd1_mini': {'D0': 16, 'D1': 5, 'D2': 4, 'D3': 0, 'D4': 2, 'D5': 14, 'D6': 12, 'D7': 13, - 'D8': 15, 'LED': 2}, - 'd1_mini_lite': 'd1_mini', - 'd1_mini_pro': 'd1_mini', - 'esp01': {}, - 'esp01_1m': {}, - 'esp07': {}, - 'esp12e': {}, - 'esp210': {}, - 'esp8285': {}, - 'esp_wroom_02': {}, - 'espduino': {'LED': 16}, - 'espectro': {'LED': 15, 'BUTTON': 2}, - 'espino': {'LED': 2, 'LED_RED': 2, 'LED_GREEN': 4, 'LED_BLUE': 5, 'BUTTON': 0}, - 'espinotee': {'LED': 16}, - 'espresso_lite_v1': {'LED': 16}, - 'espresso_lite_v2': {'LED': 2}, - 'gen4iod': {}, - 'heltec_wifi_kit_8': 'd1_mini', - 'huzzah': {'LED': 0, 'LED_RED': 0, 'LED_BLUE': 2, 'D4': 4, 'D5': 5, 'D12': 12, - 'D13': 13, 'D14': 14, 'D15': 15, 'D16': 16}, - 'inventone': {}, - 'modwifi': {}, - 'nodemcu': {'D0': 16, 'D1': 5, 'D2': 4, 'D3': 0, 'D4': 2, 'D5': 14, 'D6': 12, 'D7': 13, - 'D8': 15, 'D9': 3, 'D10': 1, 'LED': 16}, - 'nodemcuv2': 'nodemcu', - 'oak': {'P0': 2, 'P1': 5, 'P2': 0, 'P3': 3, 'P4': 1, 'P5': 4, 'P6': 15, 'P7': 13, 'P8': 12, - 'P9': 14, 'P10': 16, 'P11': 17, 'LED': 5}, - 'phoenix_v1': {'LED': 16}, - 'phoenix_v2': {'LED': 2}, - 'sparkfunBlynk': 'thing', - 'thing': {'LED': 5, 'SDA': 2, 'SCL': 14}, - 'thingdev': 'thing', - 'wifi_slot': {'LED': 2}, - 'wifiduino': {'D0': 3, 'D1': 1, 'D2': 2, 'D3': 0, 'D4': 4, 'D5': 5, 'D6': 16, 'D7': 14, - 'D8': 12, 'D9': 13, 'D10': 15, 'D11': 13, 'D12': 12, 'D13': 14}, - 'wifinfo': {'LED': 12, 'D0': 16, 'D1': 5, 'D2': 4, 'D3': 0, 'D4': 2, 'D5': 14, 'D6': 12, - 'D7': 13, 'D8': 15, 'D9': 3, 'D10': 1}, - 'wio_link': {'LED': 2, 'GROVE': 15, 'D0': 14, 'D1': 12, 'D2': 13, 'BUTTON': 0}, - 'wio_node': {'LED': 2, 'GROVE': 15, 'D0': 3, 'D1': 5, 'BUTTON': 0}, - 'xinabox_cw01': {'SDA': 2, 'SCL': 14, 'LED': 5, 'LED_RED': 12, 'LED_GREEN': 13} + "d1": { + "D0": 3, + "D1": 1, + "D2": 16, + "D3": 5, + "D4": 4, + "D5": 14, + "D6": 12, + "D7": 13, + "D8": 0, + "D9": 2, + "D10": 15, + "D11": 13, + "D12": 14, + "D13": 14, + "D14": 4, + "D15": 5, + "LED": 2, + }, + "d1_mini": { + "D0": 16, + "D1": 5, + "D2": 4, + "D3": 0, + "D4": 2, + "D5": 14, + "D6": 12, + "D7": 13, + "D8": 15, + "LED": 2, + }, + "d1_mini_lite": "d1_mini", + "d1_mini_pro": "d1_mini", + "esp01": {}, + "esp01_1m": {}, + "esp07": {}, + "esp12e": {}, + "esp210": {}, + "esp8285": {}, + "esp_wroom_02": {}, + "espduino": {"LED": 16}, + "espectro": {"LED": 15, "BUTTON": 2}, + "espino": {"LED": 2, "LED_RED": 2, "LED_GREEN": 4, "LED_BLUE": 5, "BUTTON": 0}, + "espinotee": {"LED": 16}, + "espresso_lite_v1": {"LED": 16}, + "espresso_lite_v2": {"LED": 2}, + "gen4iod": {}, + "heltec_wifi_kit_8": "d1_mini", + "huzzah": { + "LED": 0, + "LED_RED": 0, + "LED_BLUE": 2, + "D4": 4, + "D5": 5, + "D12": 12, + "D13": 13, + "D14": 14, + "D15": 15, + "D16": 16, + }, + "inventone": {}, + "modwifi": {}, + "nodemcu": { + "D0": 16, + "D1": 5, + "D2": 4, + "D3": 0, + "D4": 2, + "D5": 14, + "D6": 12, + "D7": 13, + "D8": 15, + "D9": 3, + "D10": 1, + "LED": 16, + }, + "nodemcuv2": "nodemcu", + "oak": { + "P0": 2, + "P1": 5, + "P2": 0, + "P3": 3, + "P4": 1, + "P5": 4, + "P6": 15, + "P7": 13, + "P8": 12, + "P9": 14, + "P10": 16, + "P11": 17, + "LED": 5, + }, + "phoenix_v1": {"LED": 16}, + "phoenix_v2": {"LED": 2}, + "sparkfunBlynk": "thing", + "thing": {"LED": 5, "SDA": 2, "SCL": 14}, + "thingdev": "thing", + "wifi_slot": {"LED": 2}, + "wifiduino": { + "D0": 3, + "D1": 1, + "D2": 2, + "D3": 0, + "D4": 4, + "D5": 5, + "D6": 16, + "D7": 14, + "D8": 12, + "D9": 13, + "D10": 15, + "D11": 13, + "D12": 12, + "D13": 14, + }, + "wifinfo": { + "LED": 12, + "D0": 16, + "D1": 5, + "D2": 4, + "D3": 0, + "D4": 2, + "D5": 14, + "D6": 12, + "D7": 13, + "D8": 15, + "D9": 3, + "D10": 1, + }, + "wio_link": {"LED": 2, "GROVE": 15, "D0": 14, "D1": 12, "D2": 13, "BUTTON": 0}, + "wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0}, + "xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13}, } FLASH_SIZE_1_MB = 2 ** 20 @@ -64,193 +160,711 @@ FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB ESP8266_FLASH_SIZES = { - 'd1': FLASH_SIZE_4_MB, - 'd1_mini': FLASH_SIZE_4_MB, - 'd1_mini_lite': FLASH_SIZE_1_MB, - 'd1_mini_pro': FLASH_SIZE_16_MB, - 'esp01': FLASH_SIZE_512_KB, - 'esp01_1m': FLASH_SIZE_1_MB, - 'esp07': FLASH_SIZE_4_MB, - 'esp12e': FLASH_SIZE_4_MB, - 'esp210': FLASH_SIZE_4_MB, - 'esp8285': FLASH_SIZE_1_MB, - 'esp_wroom_02': FLASH_SIZE_2_MB, - 'espduino': FLASH_SIZE_4_MB, - 'espectro': FLASH_SIZE_4_MB, - 'espino': FLASH_SIZE_4_MB, - 'espinotee': FLASH_SIZE_4_MB, - 'espresso_lite_v1': FLASH_SIZE_4_MB, - 'espresso_lite_v2': FLASH_SIZE_4_MB, - 'gen4iod': FLASH_SIZE_512_KB, - 'heltec_wifi_kit_8': FLASH_SIZE_4_MB, - 'huzzah': FLASH_SIZE_4_MB, - 'inventone': FLASH_SIZE_4_MB, - 'modwifi': FLASH_SIZE_2_MB, - 'nodemcu': FLASH_SIZE_4_MB, - 'nodemcuv2': FLASH_SIZE_4_MB, - 'oak': FLASH_SIZE_4_MB, - 'phoenix_v1': FLASH_SIZE_4_MB, - 'phoenix_v2': FLASH_SIZE_4_MB, - 'sparkfunBlynk': FLASH_SIZE_4_MB, - 'thing': FLASH_SIZE_512_KB, - 'thingdev': FLASH_SIZE_512_KB, - 'wifi_slot': FLASH_SIZE_1_MB, - 'wifiduino': FLASH_SIZE_4_MB, - 'wifinfo': FLASH_SIZE_1_MB, - 'wio_link': FLASH_SIZE_4_MB, - 'wio_node': FLASH_SIZE_4_MB, - 'xinabox_cw01': FLASH_SIZE_4_MB, + "d1": FLASH_SIZE_4_MB, + "d1_mini": FLASH_SIZE_4_MB, + "d1_mini_lite": FLASH_SIZE_1_MB, + "d1_mini_pro": FLASH_SIZE_16_MB, + "esp01": FLASH_SIZE_512_KB, + "esp01_1m": FLASH_SIZE_1_MB, + "esp07": FLASH_SIZE_4_MB, + "esp12e": FLASH_SIZE_4_MB, + "esp210": FLASH_SIZE_4_MB, + "esp8285": FLASH_SIZE_1_MB, + "esp_wroom_02": FLASH_SIZE_2_MB, + "espduino": FLASH_SIZE_4_MB, + "espectro": FLASH_SIZE_4_MB, + "espino": FLASH_SIZE_4_MB, + "espinotee": FLASH_SIZE_4_MB, + "espresso_lite_v1": FLASH_SIZE_4_MB, + "espresso_lite_v2": FLASH_SIZE_4_MB, + "gen4iod": FLASH_SIZE_512_KB, + "heltec_wifi_kit_8": FLASH_SIZE_4_MB, + "huzzah": FLASH_SIZE_4_MB, + "inventone": FLASH_SIZE_4_MB, + "modwifi": FLASH_SIZE_2_MB, + "nodemcu": FLASH_SIZE_4_MB, + "nodemcuv2": FLASH_SIZE_4_MB, + "oak": FLASH_SIZE_4_MB, + "phoenix_v1": FLASH_SIZE_4_MB, + "phoenix_v2": FLASH_SIZE_4_MB, + "sparkfunBlynk": FLASH_SIZE_4_MB, + "thing": FLASH_SIZE_512_KB, + "thingdev": FLASH_SIZE_512_KB, + "wifi_slot": FLASH_SIZE_1_MB, + "wifiduino": FLASH_SIZE_4_MB, + "wifinfo": FLASH_SIZE_1_MB, + "wio_link": FLASH_SIZE_4_MB, + "wio_node": FLASH_SIZE_4_MB, + "xinabox_cw01": FLASH_SIZE_4_MB, } ESP8266_LD_SCRIPTS = { - FLASH_SIZE_512_KB: ('eagle.flash.512k0.ld', 'eagle.flash.512k.ld'), - FLASH_SIZE_1_MB: ('eagle.flash.1m0.ld', 'eagle.flash.1m.ld'), - FLASH_SIZE_2_MB: ('eagle.flash.2m.ld', 'eagle.flash.2m.ld'), - FLASH_SIZE_4_MB: ('eagle.flash.4m.ld', 'eagle.flash.4m.ld'), - FLASH_SIZE_16_MB: ('eagle.flash.16m.ld', 'eagle.flash.16m14m.ld'), + FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"), + FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"), + FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"), + FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"), + FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"), } ESP32_BASE_PINS = { - 'TX': 1, 'RX': 3, 'SDA': 21, 'SCL': 22, 'SS': 5, 'MOSI': 23, 'MISO': 19, 'SCK': 18, 'A0': 36, - 'A3': 39, 'A4': 32, 'A5': 33, 'A6': 34, 'A7': 35, 'A10': 4, 'A11': 0, 'A12': 2, 'A13': 15, - 'A14': 13, 'A15': 12, 'A16': 14, 'A17': 27, 'A18': 25, 'A19': 26, 'T0': 4, 'T1': 0, 'T2': 2, - 'T3': 15, 'T4': 13, 'T5': 12, 'T6': 14, 'T7': 27, 'T8': 33, 'T9': 32, 'DAC1': 25, 'DAC2': 26, - 'SVP': 36, 'SVN': 39, + "TX": 1, + "RX": 3, + "SDA": 21, + "SCL": 22, + "SS": 5, + "MOSI": 23, + "MISO": 19, + "SCK": 18, + "A0": 36, + "A3": 39, + "A4": 32, + "A5": 33, + "A6": 34, + "A7": 35, + "A10": 4, + "A11": 0, + "A12": 2, + "A13": 15, + "A14": 13, + "A15": 12, + "A16": 14, + "A17": 27, + "A18": 25, + "A19": 26, + "T0": 4, + "T1": 0, + "T2": 2, + "T3": 15, + "T4": 13, + "T5": 12, + "T6": 14, + "T7": 27, + "T8": 33, + "T9": 32, + "DAC1": 25, + "DAC2": 26, + "SVP": 36, + "SVN": 39, } ESP32_BOARD_PINS = { - 'alksesp32': {'A0': 32, 'A1': 33, 'A2': 25, 'A3': 26, 'A4': 27, 'A5': 14, 'A6': 12, 'A7': 15, - 'D0': 40, 'D1': 41, 'D10': 19, 'D11': 21, 'D12': 22, 'D13': 23, 'D2': 15, - 'D3': 2, 'D4': 0, 'D5': 4, 'D6': 16, 'D7': 17, 'D8': 5, 'D9': 18, 'DHT_PIN': 26, - 'LED': 23, 'L_B': 5, 'L_G': 17, 'L_R': 22, 'L_RGB_B': 16, 'L_RGB_G': 21, - 'L_RGB_R': 4, 'L_Y': 23, 'MISO': 22, 'MOSI': 21, 'PHOTO': 25, 'PIEZO1': 19, - 'PIEZO2': 18, 'POT1': 32, 'POT2': 33, 'S1': 4, 'S2': 16, 'S3': 18, 'S4': 19, - 'S5': 21, 'SCK': 23, 'SCL': 14, 'SDA': 27, 'SS': 19, 'SW1': 15, 'SW2': 2, - 'SW3': 0}, - 'bpi-bit': {'BUTTON_A': 35, 'BUTTON_B': 27, 'BUZZER': 25, 'LIGHT_SENSOR1': 36, - 'LIGHT_SENSOR2': 39, 'MPU9250_INT': 0, 'P0': 25, 'P1': 32, 'P10': 26, 'P11': 27, - 'P12': 2, 'P13': 18, 'P14': 19, 'P15': 23, 'P16': 5, 'P19': 22, 'P2': 33, - 'P20': 21, 'P3': 13, 'P4': 15, 'P5': 35, 'P6': 12, 'P7': 14, 'P8': 16, 'P9': 17, - 'RGB_LED': 4, 'TEMPERATURE_SENSOR': 34}, - 'd-duino-32': {'D1': 5, 'D10': 1, 'D2': 4, 'D3': 0, 'D4': 2, 'D5': 14, 'D6': 12, 'D7': 13, - 'D8': 15, 'D9': 3, 'MISO': 12, 'MOSI': 13, 'SCK': 14, 'SCL': 4, 'SDA': 5, - 'SS': 15}, - 'esp-wrover-kit': {}, - 'esp32-devkitlipo': {}, - 'esp32-evb': {'BUTTON': 34, 'MISO': 15, 'MOSI': 2, 'SCK': 14, 'SCL': 16, 'SDA': 13, 'SS': 17}, - 'esp32-gateway': {'BUTTON': 34, 'LED': 33, 'SCL': 16, 'SDA': 32}, - 'esp32-poe-iso': {'BUTTON': 34, 'MISO': 15, 'MOSI': 2, 'SCK': 14, 'SCL': 16, 'SDA': 13}, - 'esp32-poe': {'BUTTON': 34, 'MISO': 15, 'MOSI': 2, 'SCK': 14, 'SCL': 16, 'SDA': 13}, - 'esp32-pro': {'BUTTON': 34, 'MISO': 15, 'MOSI': 2, 'SCK': 14, 'SCL': 16, 'SDA': 13, 'SS': 17}, - 'esp320': {'LED': 5, 'MISO': 12, 'MOSI': 13, 'SCK': 14, 'SCL': 14, 'SDA': 2, 'SS': 15}, - 'esp32cam': {}, - 'esp32dev': {}, - 'esp32doit-devkit-v1': {'LED': 2}, - 'esp32thing': {'BUTTON': 0, 'LED': 5, 'SS': 2}, - 'esp32vn-iot-uno': {}, - 'espea32': {'BUTTON': 0, 'LED': 5}, - 'espectro32': {'LED': 15, 'SD_SS': 33}, - 'espino32': {'BUTTON': 0, 'LED': 16}, - 'featheresp32': {'A0': 26, 'A1': 25, 'A10': 27, 'A11': 12, 'A12': 13, 'A13': 35, 'A2': 34, - 'A4': 36, 'A5': 4, 'A6': 14, 'A7': 32, 'A8': 15, 'A9': 33, 'Ax': 2, - 'LED': 13, 'MOSI': 18, 'RX': 16, 'SCK': 5, 'SDA': 23, 'SS': 33, 'TX': 17}, - 'firebeetle32': {'LED': 2}, - 'fm-devkit': {'D0': 34, 'D1': 35, 'D10': 0, 'D2': 32, 'D3': 33, 'D4': 27, 'D5': 14, 'D6': 12, - 'D7': 13, 'D8': 15, 'D9': 23, 'I2S_DOUT': 22, 'I2S_LRCLK': 25, 'I2S_MCLK': 2, - 'I2S_SCLK': 26, 'LED': 5, 'SCL': 17, 'SDA': 16, 'SW1': 4, 'SW2': 18, 'SW3': 19, - 'SW4': 21}, - 'frogboard': {}, - 'heltec_wifi_kit_32': {'A1': 37, 'A2': 38, 'BUTTON': 0, 'LED': 25, 'RST_OLED': 16, - 'SCL_OLED': 15, 'SDA_OLED': 4, 'Vext': 21}, - 'heltec_wifi_lora_32': {'BUTTON': 0, 'DIO0': 26, 'DIO1': 33, 'DIO2': 32, 'LED': 25, - 'MOSI': 27, 'RST_LoRa': 14, 'RST_OLED': 16, 'SCK': 5, 'SCL_OLED': 15, - 'SDA_OLED': 4, 'SS': 18, 'Vext': 21}, - 'heltec_wifi_lora_32_V2': {'BUTTON': 0, 'DIO0': 26, 'DIO1': 35, 'DIO2': 34, 'LED': 25, - 'MOSI': 27, 'RST_LoRa': 14, 'RST_OLED': 16, 'SCK': 5, - 'SCL_OLED': 15, 'SDA_OLED': 4, 'SS': 18, 'Vext': 21}, - 'heltec_wireless_stick': {'BUTTON': 0, 'DIO0': 26, 'DIO1': 35, 'DIO2': 34, 'LED': 25, - 'MOSI': 27, 'RST_LoRa': 14, 'RST_OLED': 16, 'SCK': 5, - 'SCL_OLED': 15, 'SDA_OLED': 4, 'SS': 18, 'Vext': 21}, - 'hornbill32dev': {'BUTTON': 0, 'LED': 13}, - 'hornbill32minima': {'SS': 2}, - 'intorobot': {'A1': 39, 'A2': 35, 'A3': 25, 'A4': 26, 'A5': 14, 'A6': 12, 'A7': 15, 'A8': 13, - 'A9': 2, 'BUTTON': 0, 'D0': 19, 'D1': 23, 'D2': 18, 'D3': 17, 'D4': 16, 'D5': 5, - 'D6': 4, 'LED': 4, 'MISO': 17, 'MOSI': 16, 'RGB_B_BUILTIN': 22, - 'RGB_G_BUILTIN': 21, 'RGB_R_BUILTIN': 27, 'SCL': 19, 'SDA': 23, 'T0': 19, - 'T1': 23, 'T2': 18, 'T3': 17, 'T4': 16, 'T5': 5, 'T6': 4}, - 'iotaap_magnolia': {}, - 'iotbusio': {}, - 'iotbusproteus': {}, - 'lolin32': {'LED': 5}, - 'lolin_d32': {'LED': 5, '_VBAT': 35}, - 'lolin_d32_pro': {'LED': 5, '_VBAT': 35}, - 'lopy': {'A1': 37, 'A2': 38, 'LED': 0, 'MISO': 37, 'MOSI': 22, 'SCK': 13, 'SCL': 13, - 'SDA': 12, 'SS': 17}, - 'lopy4': {'A1': 37, 'A2': 38, 'LED': 0, 'MISO': 37, 'MOSI': 22, 'SCK': 13, 'SCL': 13, - 'SDA': 12, 'SS': 18}, - 'm5stack-core-esp32': {'ADC1': 35, 'ADC2': 36, 'G0': 0, 'G1': 1, 'G12': 12, 'G13': 13, - 'G15': 15, 'G16': 16, 'G17': 17, 'G18': 18, 'G19': 19, 'G2': 2, - 'G21': 21, 'G22': 22, 'G23': 23, 'G25': 25, 'G26': 26, 'G3': 3, - 'G34': 34, 'G35': 35, 'G36': 36, 'G5': 5, 'RXD2': 16, 'TXD2': 17}, - 'm5stack-fire': {'ADC1': 35, 'ADC2': 36, 'G0': 0, 'G1': 1, 'G12': 12, 'G13': 13, 'G15': 15, - 'G16': 16, 'G17': 17, 'G18': 18, 'G19': 19, 'G2': 2, 'G21': 21, 'G22': 22, - 'G23': 23, 'G25': 25, 'G26': 26, 'G3': 3, 'G34': 34, 'G35': 35, 'G36': 36, - 'G5': 5}, - 'm5stack-grey': {'ADC1': 35, 'ADC2': 36, 'G0': 0, 'G1': 1, 'G12': 12, 'G13': 13, 'G15': 15, - 'G16': 16, 'G17': 17, 'G18': 18, 'G19': 19, 'G2': 2, 'G21': 21, 'G22': 22, - 'G23': 23, 'G25': 25, 'G26': 26, 'G3': 3, 'G34': 34, 'G35': 35, 'G36': 36, - 'G5': 5, 'RXD2': 16, 'TXD2': 17}, - 'm5stick-c': {'ADC1': 35, 'ADC2': 36, 'G0': 0, 'G10': 10, 'G26': 26, 'G32': 32, 'G33': 33, - 'G36': 36, 'G37': 37, 'G39': 39, 'G9': 9, 'MISO': 36, 'MOSI': 15, 'SCK': 13, - 'SCL': 33, 'SDA': 32}, - 'magicbit': {'BLUE_LED': 17, 'BUZZER': 25, 'GREEN_LED': 16, 'LDR': 36, 'LED': 16, - 'LEFT_BUTTON': 35, 'MOTOR1A': 27, 'MOTOR1B': 18, 'MOTOR2A': 16, 'MOTOR2B': 17, - 'POT': 39, 'RED_LED': 27, 'RIGHT_PUTTON': 34, 'YELLOW_LED': 18}, - 'mhetesp32devkit': {'LED': 2}, - 'mhetesp32minikit': {'LED': 2}, - 'microduino-core-esp32': {'A0': 12, 'A1': 13, 'A10': 25, 'A11': 26, 'A12': 27, 'A13': 14, - 'A2': 15, 'A3': 4, 'A6': 38, 'A7': 37, 'A8': 32, 'A9': 33, 'D0': 3, - 'D1': 1, 'D10': 5, 'D11': 23, 'D12': 19, 'D13': 18, 'D14': 12, - 'D15': 13, 'D16': 15, 'D17': 4, 'D18': 22, 'D19': 21, 'D2': 16, - 'D20': 38, 'D21': 37, 'D3': 17, 'D4': 32, 'D5': 33, 'D6': 25, - 'D7': 26, 'D8': 27, 'D9': 14, 'SCL': 21, 'SCL1': 13, 'SDA': 22, - 'SDA1': 12}, - 'nano32': {'BUTTON': 0, 'LED': 16}, - 'nina_w10': {'D0': 3, 'D1': 1, 'D10': 5, 'D11': 19, 'D12': 23, 'D13': 18, 'D14': 13, - 'D15': 12, 'D16': 32, 'D17': 33, 'D18': 21, 'D19': 34, 'D2': 26, 'D20': 36, - 'D21': 39, 'D3': 25, 'D4': 35, 'D5': 27, 'D6': 22, 'D7': 0, 'D8': 15, 'D9': 14, - 'LED_BLUE': 21, 'LED_GREEN': 33, 'LED_RED': 23, 'SCL': 13, 'SDA': 12, 'SW1': 33, - 'SW2': 27}, - 'node32s': {}, - 'nodemcu-32s': {'BUTTON': 0, 'LED': 2}, - 'odroid_esp32': {'ADC1': 35, 'ADC2': 36, 'LED': 2, 'SCL': 4, 'SDA': 15, 'SS': 22}, - 'onehorse32dev': {'A1': 37, 'A2': 38, 'BUTTON': 0, 'LED': 5}, - 'oroca_edubot': {'A0': 34, 'A1': 39, 'A2': 36, 'A3': 33, 'D0': 4, 'D1': 16, 'D2': 17, - 'D3': 22, 'D4': 23, 'D5': 5, 'D6': 18, 'D7': 19, 'D8': 33, 'LED': 13, - 'MOSI': 18, 'RX': 16, 'SCK': 5, 'SDA': 23, 'SS': 2, 'TX': 17, 'VBAT': 35}, - 'pico32': {}, - 'pocket_32': {'LED': 16}, - 'pycom_gpy': {'A1': 37, 'A2': 38, 'LED': 0, 'MISO': 37, 'MOSI': 22, 'SCK': 13, 'SCL': 13, - 'SDA': 12, 'SS': 17}, - 'quantum': {}, - 'sparkfun_lora_gateway_1-channel': {'MISO': 12, 'MOSI': 13, 'SCK': 14, 'SS': 16}, - 'tinypico': {}, - 'ttgo-lora32-v1': {'A1': 37, 'A2': 38, 'BUTTON': 0, 'LED': 2, 'MOSI': 27, 'SCK': 5, 'SS': 18}, - 'ttgo-t-beam': {'BUTTON': 39, 'LED': 14, 'MOSI': 27, 'SCK': 5, 'SS': 18}, - 'ttgo-t-watch': {'BUTTON': 36, 'MISO': 2, 'MOSI': 15, 'SCK': 14, 'SS': 13}, - 'ttgo-t1': {'LED': 22, 'MISO': 2, 'MOSI': 15, 'SCK': 14, 'SCL': 23, 'SS': 13}, - 'turta_iot_node': {}, - 'vintlabs-devkit-v1': {'LED': 2, 'PWM0': 12, 'PWM1': 13, 'PWM2': 14, 'PWM3': 15, 'PWM4': 16, - 'PWM5': 17, 'PWM6': 18, 'PWM7': 19}, - 'wemos_d1_mini32': {'D0': 26, 'D1': 22, 'D2': 21, 'D3': 17, 'D4': 16, 'D5': 18, 'D6': 19, - 'D7': 23, 'D8': 5, 'LED': 2, 'RXD': 3, 'TXD': 1, '_VBAT': 35}, - 'wemosbat': {'LED': 16}, - 'wesp32': {'MISO': 32, 'SCL': 4, 'SDA': 15}, - 'widora-air': {'A1': 39, 'A2': 35, 'A3': 25, 'A4': 26, 'A5': 14, 'A6': 12, 'A7': 15, 'A8': 13, - 'A9': 2, 'BUTTON': 0, 'D0': 19, 'D1': 23, 'D2': 18, 'D3': 17, 'D4': 16, - 'D5': 5, 'D6': 4, 'LED': 25, 'MISO': 17, 'MOSI': 16, 'SCL': 19, 'SDA': 23, - 'T0': 19, 'T1': 23, 'T2': 18, 'T3': 17, 'T4': 16, 'T5': 5, 'T6': 4}, - 'xinabox_cw02': {'LED': 27}, + "alksesp32": { + "A0": 32, + "A1": 33, + "A2": 25, + "A3": 26, + "A4": 27, + "A5": 14, + "A6": 12, + "A7": 15, + "D0": 40, + "D1": 41, + "D10": 19, + "D11": 21, + "D12": 22, + "D13": 23, + "D2": 15, + "D3": 2, + "D4": 0, + "D5": 4, + "D6": 16, + "D7": 17, + "D8": 5, + "D9": 18, + "DHT_PIN": 26, + "LED": 23, + "L_B": 5, + "L_G": 17, + "L_R": 22, + "L_RGB_B": 16, + "L_RGB_G": 21, + "L_RGB_R": 4, + "L_Y": 23, + "MISO": 22, + "MOSI": 21, + "PHOTO": 25, + "PIEZO1": 19, + "PIEZO2": 18, + "POT1": 32, + "POT2": 33, + "S1": 4, + "S2": 16, + "S3": 18, + "S4": 19, + "S5": 21, + "SCK": 23, + "SCL": 14, + "SDA": 27, + "SS": 19, + "SW1": 15, + "SW2": 2, + "SW3": 0, + }, + "bpi-bit": { + "BUTTON_A": 35, + "BUTTON_B": 27, + "BUZZER": 25, + "LIGHT_SENSOR1": 36, + "LIGHT_SENSOR2": 39, + "MPU9250_INT": 0, + "P0": 25, + "P1": 32, + "P10": 26, + "P11": 27, + "P12": 2, + "P13": 18, + "P14": 19, + "P15": 23, + "P16": 5, + "P19": 22, + "P2": 33, + "P20": 21, + "P3": 13, + "P4": 15, + "P5": 35, + "P6": 12, + "P7": 14, + "P8": 16, + "P9": 17, + "RGB_LED": 4, + "TEMPERATURE_SENSOR": 34, + }, + "d-duino-32": { + "D1": 5, + "D10": 1, + "D2": 4, + "D3": 0, + "D4": 2, + "D5": 14, + "D6": 12, + "D7": 13, + "D8": 15, + "D9": 3, + "MISO": 12, + "MOSI": 13, + "SCK": 14, + "SCL": 4, + "SDA": 5, + "SS": 15, + }, + "esp-wrover-kit": {}, + "esp32-devkitlipo": {}, + "esp32-evb": { + "BUTTON": 34, + "MISO": 15, + "MOSI": 2, + "SCK": 14, + "SCL": 16, + "SDA": 13, + "SS": 17, + }, + "esp32-gateway": {"BUTTON": 34, "LED": 33, "SCL": 16, "SDA": 32}, + "esp32-poe-iso": { + "BUTTON": 34, + "MISO": 15, + "MOSI": 2, + "SCK": 14, + "SCL": 16, + "SDA": 13, + }, + "esp32-poe": {"BUTTON": 34, "MISO": 15, "MOSI": 2, "SCK": 14, "SCL": 16, "SDA": 13}, + "esp32-pro": { + "BUTTON": 34, + "MISO": 15, + "MOSI": 2, + "SCK": 14, + "SCL": 16, + "SDA": 13, + "SS": 17, + }, + "esp320": { + "LED": 5, + "MISO": 12, + "MOSI": 13, + "SCK": 14, + "SCL": 14, + "SDA": 2, + "SS": 15, + }, + "esp32cam": {}, + "esp32dev": {}, + "esp32doit-devkit-v1": {"LED": 2}, + "esp32thing": {"BUTTON": 0, "LED": 5, "SS": 2}, + "esp32vn-iot-uno": {}, + "espea32": {"BUTTON": 0, "LED": 5}, + "espectro32": {"LED": 15, "SD_SS": 33}, + "espino32": {"BUTTON": 0, "LED": 16}, + "featheresp32": { + "A0": 26, + "A1": 25, + "A10": 27, + "A11": 12, + "A12": 13, + "A13": 35, + "A2": 34, + "A4": 36, + "A5": 4, + "A6": 14, + "A7": 32, + "A8": 15, + "A9": 33, + "Ax": 2, + "LED": 13, + "MOSI": 18, + "RX": 16, + "SCK": 5, + "SDA": 23, + "SS": 33, + "TX": 17, + }, + "firebeetle32": {"LED": 2}, + "fm-devkit": { + "D0": 34, + "D1": 35, + "D10": 0, + "D2": 32, + "D3": 33, + "D4": 27, + "D5": 14, + "D6": 12, + "D7": 13, + "D8": 15, + "D9": 23, + "I2S_DOUT": 22, + "I2S_LRCLK": 25, + "I2S_MCLK": 2, + "I2S_SCLK": 26, + "LED": 5, + "SCL": 17, + "SDA": 16, + "SW1": 4, + "SW2": 18, + "SW3": 19, + "SW4": 21, + }, + "frogboard": {}, + "heltec_wifi_kit_32": { + "A1": 37, + "A2": 38, + "BUTTON": 0, + "LED": 25, + "RST_OLED": 16, + "SCL_OLED": 15, + "SDA_OLED": 4, + "Vext": 21, + }, + "heltec_wifi_lora_32": { + "BUTTON": 0, + "DIO0": 26, + "DIO1": 33, + "DIO2": 32, + "LED": 25, + "MOSI": 27, + "RST_LoRa": 14, + "RST_OLED": 16, + "SCK": 5, + "SCL_OLED": 15, + "SDA_OLED": 4, + "SS": 18, + "Vext": 21, + }, + "heltec_wifi_lora_32_V2": { + "BUTTON": 0, + "DIO0": 26, + "DIO1": 35, + "DIO2": 34, + "LED": 25, + "MOSI": 27, + "RST_LoRa": 14, + "RST_OLED": 16, + "SCK": 5, + "SCL_OLED": 15, + "SDA_OLED": 4, + "SS": 18, + "Vext": 21, + }, + "heltec_wireless_stick": { + "BUTTON": 0, + "DIO0": 26, + "DIO1": 35, + "DIO2": 34, + "LED": 25, + "MOSI": 27, + "RST_LoRa": 14, + "RST_OLED": 16, + "SCK": 5, + "SCL_OLED": 15, + "SDA_OLED": 4, + "SS": 18, + "Vext": 21, + }, + "hornbill32dev": {"BUTTON": 0, "LED": 13}, + "hornbill32minima": {"SS": 2}, + "intorobot": { + "A1": 39, + "A2": 35, + "A3": 25, + "A4": 26, + "A5": 14, + "A6": 12, + "A7": 15, + "A8": 13, + "A9": 2, + "BUTTON": 0, + "D0": 19, + "D1": 23, + "D2": 18, + "D3": 17, + "D4": 16, + "D5": 5, + "D6": 4, + "LED": 4, + "MISO": 17, + "MOSI": 16, + "RGB_B_BUILTIN": 22, + "RGB_G_BUILTIN": 21, + "RGB_R_BUILTIN": 27, + "SCL": 19, + "SDA": 23, + "T0": 19, + "T1": 23, + "T2": 18, + "T3": 17, + "T4": 16, + "T5": 5, + "T6": 4, + }, + "iotaap_magnolia": {}, + "iotbusio": {}, + "iotbusproteus": {}, + "lolin32": {"LED": 5}, + "lolin32-lite": {"LED": 22}, + "lolin_d32": {"LED": 5, "_VBAT": 35}, + "lolin_d32_pro": {"LED": 5, "_VBAT": 35}, + "lopy": { + "A1": 37, + "A2": 38, + "LED": 0, + "MISO": 37, + "MOSI": 22, + "SCK": 13, + "SCL": 13, + "SDA": 12, + "SS": 17, + }, + "lopy4": { + "A1": 37, + "A2": 38, + "LED": 0, + "MISO": 37, + "MOSI": 22, + "SCK": 13, + "SCL": 13, + "SDA": 12, + "SS": 18, + }, + "m5stack-core-esp32": { + "ADC1": 35, + "ADC2": 36, + "G0": 0, + "G1": 1, + "G12": 12, + "G13": 13, + "G15": 15, + "G16": 16, + "G17": 17, + "G18": 18, + "G19": 19, + "G2": 2, + "G21": 21, + "G22": 22, + "G23": 23, + "G25": 25, + "G26": 26, + "G3": 3, + "G34": 34, + "G35": 35, + "G36": 36, + "G5": 5, + "RXD2": 16, + "TXD2": 17, + }, + "m5stack-fire": { + "ADC1": 35, + "ADC2": 36, + "G0": 0, + "G1": 1, + "G12": 12, + "G13": 13, + "G15": 15, + "G16": 16, + "G17": 17, + "G18": 18, + "G19": 19, + "G2": 2, + "G21": 21, + "G22": 22, + "G23": 23, + "G25": 25, + "G26": 26, + "G3": 3, + "G34": 34, + "G35": 35, + "G36": 36, + "G5": 5, + }, + "m5stack-grey": { + "ADC1": 35, + "ADC2": 36, + "G0": 0, + "G1": 1, + "G12": 12, + "G13": 13, + "G15": 15, + "G16": 16, + "G17": 17, + "G18": 18, + "G19": 19, + "G2": 2, + "G21": 21, + "G22": 22, + "G23": 23, + "G25": 25, + "G26": 26, + "G3": 3, + "G34": 34, + "G35": 35, + "G36": 36, + "G5": 5, + "RXD2": 16, + "TXD2": 17, + }, + "m5stick-c": { + "ADC1": 35, + "ADC2": 36, + "G0": 0, + "G10": 10, + "G26": 26, + "G32": 32, + "G33": 33, + "G36": 36, + "G37": 37, + "G39": 39, + "G9": 9, + "MISO": 36, + "MOSI": 15, + "SCK": 13, + "SCL": 33, + "SDA": 32, + }, + "magicbit": { + "BLUE_LED": 17, + "BUZZER": 25, + "GREEN_LED": 16, + "LDR": 36, + "LED": 16, + "LEFT_BUTTON": 35, + "MOTOR1A": 27, + "MOTOR1B": 18, + "MOTOR2A": 16, + "MOTOR2B": 17, + "POT": 39, + "RED_LED": 27, + "RIGHT_PUTTON": 34, + "YELLOW_LED": 18, + }, + "mhetesp32devkit": {"LED": 2}, + "mhetesp32minikit": {"LED": 2}, + "microduino-core-esp32": { + "A0": 12, + "A1": 13, + "A10": 25, + "A11": 26, + "A12": 27, + "A13": 14, + "A2": 15, + "A3": 4, + "A6": 38, + "A7": 37, + "A8": 32, + "A9": 33, + "D0": 3, + "D1": 1, + "D10": 5, + "D11": 23, + "D12": 19, + "D13": 18, + "D14": 12, + "D15": 13, + "D16": 15, + "D17": 4, + "D18": 22, + "D19": 21, + "D2": 16, + "D20": 38, + "D21": 37, + "D3": 17, + "D4": 32, + "D5": 33, + "D6": 25, + "D7": 26, + "D8": 27, + "D9": 14, + "SCL": 21, + "SCL1": 13, + "SDA": 22, + "SDA1": 12, + }, + "nano32": {"BUTTON": 0, "LED": 16}, + "nina_w10": { + "D0": 3, + "D1": 1, + "D10": 5, + "D11": 19, + "D12": 23, + "D13": 18, + "D14": 13, + "D15": 12, + "D16": 32, + "D17": 33, + "D18": 21, + "D19": 34, + "D2": 26, + "D20": 36, + "D21": 39, + "D3": 25, + "D4": 35, + "D5": 27, + "D6": 22, + "D7": 0, + "D8": 15, + "D9": 14, + "LED_BLUE": 21, + "LED_GREEN": 33, + "LED_RED": 23, + "SCL": 13, + "SDA": 12, + "SW1": 33, + "SW2": 27, + }, + "node32s": {}, + "nodemcu-32s": {"BUTTON": 0, "LED": 2}, + "odroid_esp32": {"ADC1": 35, "ADC2": 36, "LED": 2, "SCL": 4, "SDA": 15, "SS": 22}, + "onehorse32dev": {"A1": 37, "A2": 38, "BUTTON": 0, "LED": 5}, + "oroca_edubot": { + "A0": 34, + "A1": 39, + "A2": 36, + "A3": 33, + "D0": 4, + "D1": 16, + "D2": 17, + "D3": 22, + "D4": 23, + "D5": 5, + "D6": 18, + "D7": 19, + "D8": 33, + "LED": 13, + "MOSI": 18, + "RX": 16, + "SCK": 5, + "SDA": 23, + "SS": 2, + "TX": 17, + "VBAT": 35, + }, + "pico32": {}, + "pocket_32": {"LED": 16}, + "pycom_gpy": { + "A1": 37, + "A2": 38, + "LED": 0, + "MISO": 37, + "MOSI": 22, + "SCK": 13, + "SCL": 13, + "SDA": 12, + "SS": 17, + }, + "quantum": {}, + "sparkfun_lora_gateway_1-channel": {"MISO": 12, "MOSI": 13, "SCK": 14, "SS": 16}, + "tinypico": {}, + "ttgo-lora32-v1": { + "A1": 37, + "A2": 38, + "BUTTON": 0, + "LED": 2, + "MOSI": 27, + "SCK": 5, + "SS": 18, + }, + "ttgo-t-beam": {"BUTTON": 39, "LED": 14, "MOSI": 27, "SCK": 5, "SS": 18}, + "ttgo-t-watch": {"BUTTON": 36, "MISO": 2, "MOSI": 15, "SCK": 14, "SS": 13}, + "ttgo-t1": {"LED": 22, "MISO": 2, "MOSI": 15, "SCK": 14, "SCL": 23, "SS": 13}, + "ttgo-t7-v13-mini32": {"LED": 22}, + "ttgo-t7-v14-mini32": {"LED": 19}, + "turta_iot_node": {}, + "vintlabs-devkit-v1": { + "LED": 2, + "PWM0": 12, + "PWM1": 13, + "PWM2": 14, + "PWM3": 15, + "PWM4": 16, + "PWM5": 17, + "PWM6": 18, + "PWM7": 19, + }, + "wemos_d1_mini32": { + "D0": 26, + "D1": 22, + "D2": 21, + "D3": 17, + "D4": 16, + "D5": 18, + "D6": 19, + "D7": 23, + "D8": 5, + "LED": 2, + "RXD": 3, + "TXD": 1, + "_VBAT": 35, + }, + "wemosbat": {"LED": 16}, + "wesp32": {"MISO": 32, "SCL": 4, "SDA": 15}, + "widora-air": { + "A1": 39, + "A2": 35, + "A3": 25, + "A4": 26, + "A5": 14, + "A6": 12, + "A7": 15, + "A8": 13, + "A9": 2, + "BUTTON": 0, + "D0": 19, + "D1": 23, + "D2": 18, + "D3": 17, + "D4": 16, + "D5": 5, + "D6": 4, + "LED": 25, + "MISO": 17, + "MOSI": 16, + "SCL": 19, + "SDA": 23, + "T0": 19, + "T1": 23, + "T2": 18, + "T3": 17, + "T4": 16, + "T5": 5, + "T6": 4, + }, + "xinabox_cw02": {"LED": 27}, } @@ -279,24 +893,26 @@ def _lookup_pin(value): def _translate_pin(value): if isinstance(value, dict) or value is None: - raise cv.Invalid("This variable only supports pin numbers, not full pin schemas " - "(with inverted and mode).") + raise cv.Invalid( + "This variable only supports pin numbers, not full pin schemas " + "(with inverted and mode)." + ) if isinstance(value, int): return value try: return int(value) except ValueError: pass - if value.startswith('GPIO'): - return cv.Coerce(int)(value[len('GPIO'):].strip()) + if value.startswith("GPIO"): + return cv.Coerce(int)(value[len("GPIO") :].strip()) return _lookup_pin(value) _ESP_SDIO_PINS = { - 6: 'Flash Clock', - 7: 'Flash Data 0', - 8: 'Flash Data 1', - 11: 'Flash Command', + 6: "Flash Clock", + 7: "Flash Data 0", + 8: "Flash Data 1", + 11: "Flash Command", } @@ -306,11 +922,16 @@ def validate_gpio_pin(value): if value < 0 or value > 39: raise cv.Invalid(f"ESP32: Invalid pin number: {value}") if value in _ESP_SDIO_PINS: - raise cv.Invalid("This pin cannot be used on ESP32s and is already used by " - "the flash interface (function: {})".format(_ESP_SDIO_PINS[value])) + raise cv.Invalid( + "This pin cannot be used on ESP32s and is already used by " + "the flash interface (function: {})".format(_ESP_SDIO_PINS[value]) + ) if 9 <= value <= 10: - _LOGGER.warning("ESP32: Pin %s (9-10) might already be used by the " - "flash interface in QUAD IO flash mode.", value) + _LOGGER.warning( + "ESP32: Pin %s (9-10) might already be used by the " + "flash interface in QUAD IO flash mode.", + value, + ) if value in (20, 24, 28, 29, 30, 31): # These pins are not exposed in GPIO mux (reason unknown) # but they're missing from IO_MUX list in datasheet @@ -320,11 +941,16 @@ def validate_gpio_pin(value): if value < 0 or value > 17: raise cv.Invalid(f"ESP8266: Invalid pin number: {value}") if value in _ESP_SDIO_PINS: - raise cv.Invalid("This pin cannot be used on ESP8266s and is already used by " - "the flash interface (function: {})".format(_ESP_SDIO_PINS[value])) + raise cv.Invalid( + "This pin cannot be used on ESP8266s and is already used by " + "the flash interface (function: {})".format(_ESP_SDIO_PINS[value]) + ) if 9 <= value <= 10: - _LOGGER.warning("ESP8266: Pin %s (9-10) might already be used by the " - "flash interface in QUAD IO flash mode.", value) + _LOGGER.warning( + "ESP8266: Pin %s (9-10) might already be used by the " + "flash interface in QUAD IO flash mode.", + value, + ) return value raise NotImplementedError @@ -342,8 +968,10 @@ def input_pullup_pin(value): return output_pin(value) if CORE.is_esp8266: if value == 0: - raise cv.Invalid("GPIO Pin 0 does not support pullup pin mode. " - "Please choose another pin.") + raise cv.Invalid( + "GPIO Pin 0 does not support pullup pin mode. " + "Please choose another pin." + ) return value raise NotImplementedError @@ -352,8 +980,10 @@ def output_pin(value): value = validate_gpio_pin(value) if CORE.is_esp32: if 34 <= value <= 39: - raise cv.Invalid("ESP32: GPIO{} (34-39) can only be used as an " - "input pin.".format(value)) + raise cv.Invalid( + "ESP32: GPIO{} (34-39) can only be used as an " + "input pin.".format(value) + ) return value if CORE.is_esp8266: if value == 17: @@ -378,15 +1008,37 @@ def analog_pin(value): input_output_pin = cv.All(input_pin, output_pin) PIN_MODES_ESP8266 = [ - 'INPUT', 'OUTPUT', 'INPUT_PULLUP', 'OUTPUT_OPEN_DRAIN', 'SPECIAL', 'FUNCTION_1', - 'FUNCTION_2', 'FUNCTION_3', 'FUNCTION_4', - 'FUNCTION_0', 'WAKEUP_PULLUP', 'WAKEUP_PULLDOWN', 'INPUT_PULLDOWN_16', + "INPUT", + "OUTPUT", + "INPUT_PULLUP", + "OUTPUT_OPEN_DRAIN", + "SPECIAL", + "FUNCTION_1", + "FUNCTION_2", + "FUNCTION_3", + "FUNCTION_4", + "FUNCTION_0", + "WAKEUP_PULLUP", + "WAKEUP_PULLDOWN", + "INPUT_PULLDOWN_16", ] PIN_MODES_ESP32 = [ - 'INPUT', 'OUTPUT', 'INPUT_PULLUP', 'OUTPUT_OPEN_DRAIN', 'SPECIAL', 'FUNCTION_1', - 'FUNCTION_2', 'FUNCTION_3', 'FUNCTION_4', - 'PULLUP', 'PULLDOWN', 'INPUT_PULLDOWN', 'OPEN_DRAIN', 'FUNCTION_5', - 'FUNCTION_6', 'ANALOG', + "INPUT", + "OUTPUT", + "INPUT_PULLUP", + "OUTPUT_OPEN_DRAIN", + "SPECIAL", + "FUNCTION_1", + "FUNCTION_2", + "FUNCTION_3", + "FUNCTION_4", + "PULLUP", + "PULLDOWN", + "INPUT_PULLDOWN", + "OPEN_DRAIN", + "FUNCTION_5", + "FUNCTION_6", + "ANALOG", ] @@ -398,28 +1050,36 @@ def pin_mode(value): raise NotImplementedError -GPIO_FULL_OUTPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_NUMBER): output_pin, - cv.Optional(CONF_MODE, default='OUTPUT'): pin_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) - -GPIO_FULL_INPUT_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_NUMBER): input_pin, - cv.Optional(CONF_MODE, default='INPUT'): pin_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) - -GPIO_FULL_INPUT_PULLUP_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_NUMBER): input_pin, - cv.Optional(CONF_MODE, default='INPUT_PULLUP'): pin_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, -}) - -GPIO_FULL_ANALOG_PIN_SCHEMA = cv.Schema({ - cv.Required(CONF_NUMBER): analog_pin, - cv.Optional(CONF_MODE, default='INPUT'): pin_mode, -}) +GPIO_FULL_OUTPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_NUMBER): output_pin, + cv.Optional(CONF_MODE, default="OUTPUT"): pin_mode, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) + +GPIO_FULL_INPUT_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_NUMBER): input_pin, + cv.Optional(CONF_MODE, default="INPUT"): pin_mode, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) + +GPIO_FULL_INPUT_PULLUP_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_NUMBER): input_pin, + cv.Optional(CONF_MODE, default="INPUT_PULLUP"): pin_mode, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) + +GPIO_FULL_ANALOG_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_NUMBER): analog_pin, + cv.Optional(CONF_MODE, default="INPUT"): pin_mode, + } +) def shorthand_output_pin(value): @@ -434,10 +1094,12 @@ def shorthand_input_pin(value): def shorthand_input_pullup_pin(value): value = input_pullup_pin(value) - return GPIO_FULL_INPUT_PIN_SCHEMA({ - CONF_NUMBER: value, - CONF_MODE: 'INPUT_PULLUP', - }) + return GPIO_FULL_INPUT_PIN_SCHEMA( + { + CONF_NUMBER: value, + CONF_MODE: "INPUT_PULLUP", + } + ) def shorthand_analog_pin(value): @@ -448,8 +1110,10 @@ def shorthand_analog_pin(value): def validate_has_interrupt(value): if CORE.is_esp8266: if value[CONF_NUMBER] >= 16: - raise cv.Invalid("Pins GPIO16 and GPIO17 do not support interrupts and cannot be used " - "here, got {}".format(value[CONF_NUMBER])) + raise cv.Invalid( + "Pins GPIO16 and GPIO17 do not support interrupts and cannot be used " + "here, got {}".format(value[CONF_NUMBER]) + ) return value diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 1cda141a5447..87ca12c9a86a 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -24,6 +24,7 @@ def patch_structhash(): def patched_clean_build_dir(build_dir, *args): from platformio import fs from platformio.project.helpers import get_project_dir + platformio_ini = join(get_project_dir(), "platformio.ini") # if project's config is modified @@ -38,54 +39,59 @@ def patched_clean_build_dir(build_dir, *args): command.clean_build_dir = patched_clean_build_dir -IGNORE_LIB_WARNINGS = r'(?:' + '|'.join(['Hash', 'Update']) + r')' +IGNORE_LIB_WARNINGS = r"(?:" + "|".join(["Hash", "Update"]) + r")" FILTER_PLATFORMIO_LINES = [ - r'Verbose mode can be enabled via `-v, --verbose` option.*', - r'CONFIGURATION: https://docs.platformio.org/.*', - r'PLATFORM: .*', - r'DEBUG: Current.*', - r'PACKAGES: .*', - r'LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf.*', - r'LDF Modes: Finder ~ chain, Compatibility ~ soft.*', - r'Looking for ' + IGNORE_LIB_WARNINGS + r' library in registry', - r"Warning! Library `.*'" + IGNORE_LIB_WARNINGS + - r".*` has not been found in PlatformIO Registry.", - r"You can ignore this message, if `.*" + IGNORE_LIB_WARNINGS + r".*` is a built-in library.*", - r'Scanning dependencies...', + r"Verbose mode can be enabled via `-v, --verbose` option.*", + r"CONFIGURATION: https://docs.platformio.org/.*", + r"PLATFORM: .*", + r"DEBUG: Current.*", + r"PACKAGES: .*", + r"LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf.*", + r"LDF Modes: Finder ~ chain, Compatibility ~ soft.*", + r"Looking for " + IGNORE_LIB_WARNINGS + r" library in registry", + r"Warning! Library `.*'" + + IGNORE_LIB_WARNINGS + + r".*` has not been found in PlatformIO Registry.", + r"You can ignore this message, if `.*" + + IGNORE_LIB_WARNINGS + + r".*` is a built-in library.*", + r"Scanning dependencies...", r"Found \d+ compatible libraries", - r'Memory Usage -> http://bit.ly/pio-memory-usage', - r'esptool.py v.*', + r"Memory Usage -> http://bit.ly/pio-memory-usage", + r"esptool.py v.*", r"Found: https://platformio.org/lib/show/.*", r"Using cache: .*", - r'Installing dependencies', - r'.* @ .* is already installed', - r'Building in .* mode', - r'Advanced Memory Usage is available via .*', + r"Installing dependencies", + r".* @ .* is already installed", + r"Building in .* mode", + r"Advanced Memory Usage is available via .*", ] def run_platformio_cli(*args, **kwargs) -> Union[str, int]: os.environ["PLATFORMIO_FORCE_COLOR"] = "true" os.environ["PLATFORMIO_BUILD_DIR"] = os.path.abspath(CORE.relative_pioenvs_path()) - os.environ["PLATFORMIO_LIBDEPS_DIR"] = os.path.abspath(CORE.relative_piolibdeps_path()) - cmd = ['platformio'] + list(args) + os.environ["PLATFORMIO_LIBDEPS_DIR"] = os.path.abspath( + CORE.relative_piolibdeps_path() + ) + cmd = ["platformio"] + list(args) if not CORE.verbose: - kwargs['filter_lines'] = FILTER_PLATFORMIO_LINES + kwargs["filter_lines"] = FILTER_PLATFORMIO_LINES - if os.environ.get('ESPHOME_USE_SUBPROCESS') is not None: + if os.environ.get("ESPHOME_USE_SUBPROCESS") is not None: return run_external_process(*cmd, **kwargs) import platformio.__main__ + patch_structhash() - return run_external_command(platformio.__main__.main, - *cmd, **kwargs) + return run_external_command(platformio.__main__.main, *cmd, **kwargs) def run_platformio_cli_run(config, verbose, *args, **kwargs) -> Union[str, int]: - command = ['run', '-d', CORE.build_path] + command = ["run", "-d", CORE.build_path] if verbose: - command += ['-v'] + command += ["-v"] command += list(args) return run_platformio_cli(*command, **kwargs) @@ -95,11 +101,13 @@ def run_compile(config, verbose): def run_upload(config, verbose, port): - return run_platformio_cli_run(config, verbose, '-t', 'upload', '--upload-port', port) + return run_platformio_cli_run( + config, verbose, "-t", "upload", "--upload-port", port + ) def run_idedata(config): - args = ['-t', 'idedata'] + args = ["-t", "idedata"] stdout = run_platformio_cli_run(config, False, *args, capture_stdout=True) match = re.search(r'{\s*".*}', stdout) if match is None: @@ -129,10 +137,10 @@ def get_idedata(config): 0: "Illegal instruction (Is the flash damaged?)", 1: "SYSCALL instruction", 2: "InstructionFetchError: Processor internal physical address or data error during " - "instruction fetch", + "instruction fetch", 3: "LoadStoreError: Processor internal physical address or data error during load or store", 4: "Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT " - "register", + "register", 5: "Alloca: MOVSP instruction, if caller's registers are not in the register file", 6: "Integer Divide By Zero", 7: "reserved", @@ -147,17 +155,17 @@ def get_idedata(config): 16: "InstTLBMiss: Error during Instruction TLB refill", 17: "InstTLBMultiHit: Multiple instruction TLB entries matched", 18: "InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level " - "less than CRING", + "less than CRING", 19: "reserved", 20: "InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute " - "that does not permit instruction fetch", + "that does not permit instruction fetch", 21: "reserved", 22: "reserved", 23: "reserved", 24: "LoadStoreTLBMiss: Error during TLB refill for a load or store", 25: "LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store", 26: "LoadStorePrivilege: A load or store referenced a virtual address at a ring level less " - "than ", + "than ", 27: "reserved", 28: "Access to invalid address: LOAD (wild pointer?)", 29: "Access to invalid address: STORE (wild pointer?)", @@ -169,7 +177,7 @@ def _decode_pc(config, addr): if not idedata.addr2line_path or not idedata.firmware_elf_path: _LOGGER.debug("decode_pc no addr2line") return - command = [idedata.addr2line_path, '-pfiaC', '-e', idedata.firmware_elf_path, addr] + command = [idedata.addr2line_path, "-pfiaC", "-e", idedata.firmware_elf_path, addr] try: translation = subprocess.check_output(command).decode().strip() except Exception: # pylint: disable=broad-except @@ -179,7 +187,7 @@ def _decode_pc(config, addr): if "?? ??:0" in translation: # Nothing useful return - translation = translation.replace(' at ??:?', '').replace(':?', '') + translation = translation.replace(" at ??:?", "").replace(":?", "") _LOGGER.warning("Decoded %s", translation) @@ -189,15 +197,19 @@ def _parse_register(config, regex, line): _decode_pc(config, match.group(1)) -STACKTRACE_ESP8266_EXCEPTION_TYPE_RE = re.compile(r'[eE]xception \((\d+)\):') -STACKTRACE_ESP8266_PC_RE = re.compile(r'epc1=0x(4[0-9a-fA-F]{7})') -STACKTRACE_ESP8266_EXCVADDR_RE = re.compile(r'excvaddr=0x(4[0-9a-fA-F]{7})') -STACKTRACE_ESP32_PC_RE = re.compile(r'PC\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})') -STACKTRACE_ESP32_EXCVADDR_RE = re.compile(r'EXCVADDR\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})') -STACKTRACE_BAD_ALLOC_RE = re.compile(r'^last failed alloc call: (4[0-9a-fA-F]{7})\((\d+)\)$') -STACKTRACE_ESP32_BACKTRACE_RE = re.compile(r'Backtrace:(?:\s+0x[0-9a-fA-F]{8}:0x[0-9a-fA-F]{8})+') -STACKTRACE_ESP32_BACKTRACE_PC_RE = re.compile(r'4[0-9a-f]{7}') -STACKTRACE_ESP8266_BACKTRACE_PC_RE = re.compile(r'4[0-9a-f]{7}') +STACKTRACE_ESP8266_EXCEPTION_TYPE_RE = re.compile(r"[eE]xception \((\d+)\):") +STACKTRACE_ESP8266_PC_RE = re.compile(r"epc1=0x(4[0-9a-fA-F]{7})") +STACKTRACE_ESP8266_EXCVADDR_RE = re.compile(r"excvaddr=0x(4[0-9a-fA-F]{7})") +STACKTRACE_ESP32_PC_RE = re.compile(r"PC\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})") +STACKTRACE_ESP32_EXCVADDR_RE = re.compile(r"EXCVADDR\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})") +STACKTRACE_BAD_ALLOC_RE = re.compile( + r"^last failed alloc call: (4[0-9a-fA-F]{7})\((\d+)\)$" +) +STACKTRACE_ESP32_BACKTRACE_RE = re.compile( + r"Backtrace:(?:\s+0x[0-9a-fA-F]{8}:0x[0-9a-fA-F]{8})+" +) +STACKTRACE_ESP32_BACKTRACE_PC_RE = re.compile(r"4[0-9a-f]{7}") +STACKTRACE_ESP8266_BACKTRACE_PC_RE = re.compile(r"4[0-9a-f]{7}") def process_stacktrace(config, line, backtrace_state): @@ -206,7 +218,9 @@ def process_stacktrace(config, line, backtrace_state): match = re.match(STACKTRACE_ESP8266_EXCEPTION_TYPE_RE, line) if match is not None: code = int(match.group(1)) - _LOGGER.warning("Exception type: %s", ESP8266_EXCEPTION_CODES.get(code, 'unknown')) + _LOGGER.warning( + "Exception type: %s", ESP8266_EXCEPTION_CODES.get(code, "unknown") + ) # ESP8266 PC/EXCVADDR _parse_register(config, STACKTRACE_ESP8266_PC_RE, line) @@ -218,8 +232,9 @@ def process_stacktrace(config, line, backtrace_state): # bad alloc match = re.match(STACKTRACE_BAD_ALLOC_RE, line) if match is not None: - _LOGGER.warning("Memory allocation of %s bytes failed at %s", - match.group(2), match.group(1)) + _LOGGER.warning( + "Memory allocation of %s bytes failed at %s", match.group(2), match.group(1) + ) _decode_pc(config, match.group(1)) # ESP32 single-line backtrace @@ -230,11 +245,11 @@ def process_stacktrace(config, line, backtrace_state): _decode_pc(config, addr.group()) # ESP8266 multi-line backtrace - if '>>>stack>>>' in line: + if ">>>stack>>>" in line: # Start of backtrace backtrace_state = True _LOGGER.warning("Found stack trace! Trying to decode it") - elif '<< str - return CORE.relative_config_path('.esphome', f'{CORE.config_filename}.json') + return CORE.relative_config_path(".esphome", f"{CORE.config_filename}.json") def ext_storage_path(base_path, config_filename): # type: (str, str) -> str - return os.path.join(base_path, '.esphome', f'{config_filename}.json') + return os.path.join(base_path, ".esphome", f"{config_filename}.json") def esphome_storage_path(base_path): # type: (str) -> str - return os.path.join(base_path, '.esphome', 'esphome.json') + return os.path.join(base_path, ".esphome", "esphome.json") def trash_storage_path(base_path): # type: (str) -> str - return os.path.join(base_path, '.esphome', 'trash') + return os.path.join(base_path, ".esphome", "trash") # pylint: disable=too-many-instance-attributes class StorageJSON: - def __init__(self, storage_version, name, comment, esphome_version, - src_version, arduino_version, address, esp_platform, board, build_path, - firmware_bin_path, loaded_integrations): + def __init__( + self, + storage_version, + name, + comment, + esphome_version, + src_version, + arduino_version, + address, + esp_platform, + board, + build_path, + firmware_bin_path, + loaded_integrations, + ): # Version of the storage JSON schema assert storage_version is None or isinstance(storage_version, int) self.storage_version = storage_version # type: int @@ -63,33 +75,35 @@ def __init__(self, storage_version, name, comment, esphome_version, # The absolute path to the firmware binary self.firmware_bin_path = firmware_bin_path # type: str # A list of strings of names of loaded integrations - self.loaded_integrations = loaded_integrations # type: List[str] + self.loaded_integrations = loaded_integrations # type: List[str] self.loaded_integrations.sort() def as_dict(self): return { - 'storage_version': self.storage_version, - 'name': self.name, - 'comment': self.comment, - 'esphome_version': self.esphome_version, - 'src_version': self.src_version, - 'arduino_version': self.arduino_version, - 'address': self.address, - 'esp_platform': self.esp_platform, - 'board': self.board, - 'build_path': self.build_path, - 'firmware_bin_path': self.firmware_bin_path, - 'loaded_integrations': self.loaded_integrations, + "storage_version": self.storage_version, + "name": self.name, + "comment": self.comment, + "esphome_version": self.esphome_version, + "src_version": self.src_version, + "arduino_version": self.arduino_version, + "address": self.address, + "esp_platform": self.esp_platform, + "board": self.board, + "build_path": self.build_path, + "firmware_bin_path": self.firmware_bin_path, + "loaded_integrations": self.loaded_integrations, } def to_json(self): - return json.dumps(self.as_dict(), indent=2) + '\n' + return json.dumps(self.as_dict(), indent=2) + "\n" def save(self, path): write_file_if_changed(path, self.to_json()) @staticmethod - def from_esphome_core(esph, old): # type: (CoreType, Optional[StorageJSON]) -> StorageJSON + def from_esphome_core( + esph, old + ): # type: (CoreType, Optional[StorageJSON]) -> StorageJSON return StorageJSON( storage_version=1, name=esph.name, @@ -125,23 +139,36 @@ def from_wizard(name, address, esp_platform, board): @staticmethod def _load_impl(path): # type: (str) -> Optional[StorageJSON] - with codecs.open(path, 'r', encoding='utf-8') as f_handle: + with codecs.open(path, "r", encoding="utf-8") as f_handle: storage = json.load(f_handle) - storage_version = storage['storage_version'] - name = storage.get('name') - comment = storage.get('comment') - esphome_version = storage.get('esphome_version', storage.get('esphomeyaml_version')) - src_version = storage.get('src_version') - arduino_version = storage.get('arduino_version') - address = storage.get('address') - esp_platform = storage.get('esp_platform') - board = storage.get('board') - build_path = storage.get('build_path') - firmware_bin_path = storage.get('firmware_bin_path') - loaded_integrations = storage.get('loaded_integrations', []) - return StorageJSON(storage_version, name, comment, esphome_version, - src_version, arduino_version, address, esp_platform, board, build_path, - firmware_bin_path, loaded_integrations) + storage_version = storage["storage_version"] + name = storage.get("name") + comment = storage.get("comment") + esphome_version = storage.get( + "esphome_version", storage.get("esphomeyaml_version") + ) + src_version = storage.get("src_version") + arduino_version = storage.get("arduino_version") + address = storage.get("address") + esp_platform = storage.get("esp_platform") + board = storage.get("board") + build_path = storage.get("build_path") + firmware_bin_path = storage.get("firmware_bin_path") + loaded_integrations = storage.get("loaded_integrations", []) + return StorageJSON( + storage_version, + name, + comment, + esphome_version, + src_version, + arduino_version, + address, + esp_platform, + board, + build_path, + firmware_bin_path, + loaded_integrations, + ) @staticmethod def load(path): # type: (str) -> Optional[StorageJSON] @@ -155,8 +182,9 @@ def __eq__(self, o): # type: (Any) -> bool class EsphomeStorageJSON: - def __init__(self, storage_version, cookie_secret, last_update_check, - remote_version): + def __init__( + self, storage_version, cookie_secret, last_update_check, remote_version + ): # Version of the storage JSON schema assert storage_version is None or isinstance(storage_version, int) self.storage_version = storage_version # type: int @@ -169,10 +197,10 @@ def __init__(self, storage_version, cookie_secret, last_update_check, def as_dict(self): # type: () -> dict return { - 'storage_version': self.storage_version, - 'cookie_secret': self.cookie_secret, - 'last_update_check': self.last_update_check_str, - 'remote_version': self.remote_version, + "storage_version": self.storage_version, + "cookie_secret": self.cookie_secret, + "last_update_check": self.last_update_check_str, + "remote_version": self.remote_version, } @property @@ -187,21 +215,22 @@ def last_update_check(self, new): # type: (datetime) -> None self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S") def to_json(self): # type: () -> dict - return json.dumps(self.as_dict(), indent=2) + '\n' + return json.dumps(self.as_dict(), indent=2) + "\n" def save(self, path): # type: (str) -> None write_file_if_changed(path, self.to_json()) @staticmethod def _load_impl(path): # type: (str) -> Optional[EsphomeStorageJSON] - with codecs.open(path, 'r', encoding='utf-8') as f_handle: + with codecs.open(path, "r", encoding="utf-8") as f_handle: storage = json.load(f_handle) - storage_version = storage['storage_version'] - cookie_secret = storage.get('cookie_secret') - last_update_check = storage.get('last_update_check') - remote_version = storage.get('remote_version') - return EsphomeStorageJSON(storage_version, cookie_secret, last_update_check, - remote_version) + storage_version = storage["storage_version"] + cookie_secret = storage.get("cookie_secret") + last_update_check = storage.get("last_update_check") + remote_version = storage.get("remote_version") + return EsphomeStorageJSON( + storage_version, cookie_secret, last_update_check, remote_version + ) @staticmethod def load(path): # type: (str) -> Optional[EsphomeStorageJSON] diff --git a/esphome/util.py b/esphome/util.py index 3fe7ba8eb8eb..10f9923c447b 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -24,11 +24,13 @@ def __init__(self, name, fun, type_id, schema): @property def coroutine_fun(self): from esphome.core import coroutine + return coroutine(self.fun) @property def schema(self): from esphome.config_validation import Schema + return Schema(self.raw_schema) @@ -60,7 +62,7 @@ def safe_print(message=""): if CORE.dashboard: try: - message = message.replace('\033', '\\033') + message = message.replace("\033", "\\033") except UnicodeEncodeError: pass @@ -71,10 +73,10 @@ def safe_print(message=""): pass try: - print(message.encode('utf-8', 'backslashreplace')) + print(message.encode("utf-8", "backslashreplace")) except UnicodeEncodeError: try: - print(message.encode('ascii', 'backslashreplace')) + print(message.encode("ascii", "backslashreplace")) except UnicodeEncodeError: print("Cannot print line because of invalid locale!") @@ -82,13 +84,13 @@ def safe_print(message=""): def shlex_quote(s): if not s: return "''" - if re.search(r'[^\w@%+=:,./-]', s) is None: + if re.search(r"[^\w@%+=:,./-]", s) is None: return s return "'" + s.replace("'", "'\"'\"'") + "'" -ANSI_ESCAPE = re.compile(r'\033[@-_][0-?]*[ -/]*[@-~]') +ANSI_ESCAPE = re.compile(r"\033[@-_][0-?]*[ -/]*[@-~]") class RedirectText: @@ -97,9 +99,9 @@ def __init__(self, out, filter_lines=None): if filter_lines is None: self._filter_pattern = None else: - pattern = r'|'.join(r'(?:' + pattern + r')' for pattern in filter_lines) + pattern = r"|".join(r"(?:" + pattern + r")" for pattern in filter_lines) self._filter_pattern = re.compile(pattern) - self._line_buffer = '' + self._line_buffer = "" def __getattr__(self, item): return getattr(self._out, item) @@ -112,7 +114,7 @@ def _write_color_replace(self, s): # work. The shell we create in the dashboard is not a tty, so python removes # all color codes from the resulting stream. We just convert them to something # we can easily recognize later here. - s = s.replace('\033', '\\033') + s = s.replace("\033", "\\033") self._out.write(s) def write(self, s): @@ -128,13 +130,13 @@ def write(self, s): self._line_buffer += s lines = self._line_buffer.splitlines(True) for line in lines: - if '\n' not in line and '\r' not in line: + if "\n" not in line and "\r" not in line: # Not a complete line, set line buffer self._line_buffer = line break - self._line_buffer = '' + self._line_buffer = "" - line_without_ansi = ANSI_ESCAPE.sub('', line) + line_without_ansi = ANSI_ESCAPE.sub("", line) line_without_end = line_without_ansi.rstrip() if self._filter_pattern.match(line_without_end) is not None: # Filter pattern matched, ignore the line @@ -154,9 +156,9 @@ def isatty(self): return True -def run_external_command(func, *cmd, - capture_stdout: bool = False, - filter_lines: str = None) -> Union[int, str]: +def run_external_command( + func, *cmd, capture_stdout: bool = False, filter_lines: str = None +) -> Union[int, str]: """ Run a function from an external package that acts like a main method. @@ -169,12 +171,13 @@ def run_external_command(func, *cmd, :return: str if `capture_stdout` is set else int exit code. """ + def mock_exit(return_code): raise SystemExit(return_code) orig_argv = sys.argv orig_exit = sys.exit # mock sys.exit - full_cmd = ' '.join(shlex_quote(x) for x in cmd) + full_cmd = " ".join(shlex_quote(x) for x in cmd) _LOGGER.info("Running: %s", full_cmd) orig_stdout = sys.stdout @@ -210,11 +213,11 @@ def mock_exit(return_code): def run_external_process(*cmd, **kwargs): - full_cmd = ' '.join(shlex_quote(x) for x in cmd) + full_cmd = " ".join(shlex_quote(x) for x in cmd) _LOGGER.info("Running: %s", full_cmd) - filter_lines = kwargs.get('filter_lines') + filter_lines = kwargs.get("filter_lines") - capture_stdout = kwargs.get('capture_stdout', False) + capture_stdout = kwargs.get("capture_stdout", False) if capture_stdout: sub_stdout = io.BytesIO() else: @@ -223,9 +226,7 @@ def run_external_process(*cmd, **kwargs): sub_stderr = RedirectText(sys.stderr, filter_lines=filter_lines) try: - return subprocess.call(cmd, - stdout=sub_stdout, - stderr=sub_stderr) + return subprocess.call(cmd, stdout=sub_stdout, stderr=sub_stderr) except Exception as err: # pylint: disable=broad-except _LOGGER.error("Running command failed: %s", err) _LOGGER.error("Please try running %s locally.", full_cmd) @@ -237,7 +238,7 @@ def run_external_process(*cmd, **kwargs): def is_dev_esphome_version(): - return 'dev' in const.__version__ + return "dev" in const.__version__ # Custom OrderedDict with nicer repr method for debugging @@ -253,9 +254,9 @@ def list_yaml_files(folder): def filter_yaml_files(files): - files = [f for f in files if os.path.splitext(f)[1] == '.yaml'] - files = [f for f in files if os.path.basename(f) != 'secrets.yaml'] - files = [f for f in files if not os.path.basename(f).startswith('.')] + files = [f for f in files if os.path.splitext(f)[1] == ".yaml"] + files = [f for f in files if os.path.basename(f) != "secrets.yaml"] + files = [f for f in files if not os.path.basename(f).startswith(".")] return files @@ -268,6 +269,7 @@ def __init__(self, path: str, description: str): # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py def get_serial_ports() -> List[SerialPort]: from serial.tools.list_ports import comports + result = [] for port, desc, info in comports(include_links=True): if not port: @@ -277,13 +279,13 @@ def get_serial_ports() -> List[SerialPort]: # Also add objects in /dev/serial/by-id/ # ref: https://github.com/esphome/issues/issues/1346 - by_id_path = Path('/dev/serial/by-id') - if sys.platform.lower().startswith('linux') and by_id_path.exists(): + by_id_path = Path("/dev/serial/by-id") + if sys.platform.lower().startswith("linux") and by_id_path.exists(): from serial.tools.list_ports_linux import SysFS - for path in by_id_path.glob('*'): + for path in by_id_path.glob("*"): device = SysFS(path) - if device.subsystem == 'platform': + if device.subsystem == "platform": result.append(SerialPort(path=str(path), description=info[1])) result.sort(key=lambda x: x.path) diff --git a/esphome/voluptuous_schema.py b/esphome/voluptuous_schema.py index d10801fb9515..0fdae423cf2c 100644 --- a/esphome/voluptuous_schema.py +++ b/esphome/voluptuous_schema.py @@ -2,11 +2,12 @@ import itertools import voluptuous as vol +from esphome.jsonschema import jschema_extended class ExtraKeysInvalid(vol.Invalid): def __init__(self, *arg, **kwargs): - self.candidates = kwargs.pop('candidates') + self.candidates = kwargs.pop("candidates") vol.Invalid.__init__(self, *arg, **kwargs) @@ -19,7 +20,10 @@ def ensure_multiple_invalid(err): # pylint: disable=protected-access, unidiomatic-typecheck class _Schema(vol.Schema): """Custom cv.Schema that prints similar keys on error.""" - def __init__(self, schema, required=False, extra=vol.PREVENT_EXTRA, extra_schemas=None): + + def __init__( + self, schema, required=False, extra=vol.PREVENT_EXTRA, extra_schemas=None + ): super().__init__(schema, required=required, extra=extra) # List of extra schemas to apply after validation # Should be used sparingly, as it's not a very voluptuous-way/clean way of @@ -37,7 +41,7 @@ def __call__(self, data): return res def _compile_mapping(self, schema, invalid_msg=None): - invalid_msg = invalid_msg or 'mapping value' + invalid_msg = invalid_msg or "mapping value" # Check some things that ESPHome's schemas do not allow # mostly to keep the logic in this method sane (so these may be re-added if needed). @@ -47,7 +51,9 @@ def _compile_mapping(self, schema, invalid_msg=None): if isinstance(key, vol.Remove): raise ValueError("ESPHome does not allow vol.Remove") if isinstance(key, vol.primitive_types): - raise ValueError("All schema keys must be wrapped in cv.Required or cv.Optional") + raise ValueError( + "All schema keys must be wrapped in cv.Required or cv.Optional" + ) # Keys that may be required all_required_keys = {key for key in schema if isinstance(key, vol.Required)} @@ -64,7 +70,9 @@ def _compile_mapping(self, schema, invalid_msg=None): _compiled_schema[skey] = (new_key, new_value) # Sort compiled schema (probably not necessary for esphome, but leave it here just in case) - candidates = list(vol.schema_builder._iterate_mapping_candidates(_compiled_schema)) + candidates = list( + vol.schema_builder._iterate_mapping_candidates(_compiled_schema) + ) # After we have the list of candidates in the correct order, we want to apply some # optimization so that each @@ -75,8 +83,13 @@ def _compile_mapping(self, schema, invalid_msg=None): for skey, (ckey, cvalue) in candidates: if type(skey) in vol.primitive_types: candidates_by_key.setdefault(skey, []).append((skey, (ckey, cvalue))) - elif isinstance(skey, vol.Marker) and type(skey.schema) in vol.primitive_types: - candidates_by_key.setdefault(skey.schema, []).append((skey, (ckey, cvalue))) + elif ( + isinstance(skey, vol.Marker) + and type(skey.schema) in vol.primitive_types + ): + candidates_by_key.setdefault(skey.schema, []).append( + (skey, (ckey, cvalue)) + ) else: # These are wildcards such as 'int', 'str', 'Remove' and others which should be # applied to all keys @@ -101,7 +114,10 @@ def validate_mapping(path, iterable, out): # Insert default values for non-existing keys. for key in all_default_keys: - if not isinstance(key.default, vol.Undefined) and key.schema not in key_value_map: + if ( + not isinstance(key.default, vol.Undefined) + and key.schema not in key_value_map + ): # A default value has been specified for this missing key, insert it. key_value_map[key.schema] = key.default() @@ -110,8 +126,9 @@ def validate_mapping(path, iterable, out): for key, value in key_value_map.items(): key_path = path + [key] # Optimization. Validate against the matching key first, then fallback to the rest - relevant_candidates = itertools.chain(candidates_by_key.get(key, []), - additional_candidates) + relevant_candidates = itertools.chain( + candidates_by_key.get(key, []), additional_candidates + ) # compare each given key/value against all compiled key/values # schema key, (compiled key, compiled value) @@ -158,14 +175,21 @@ def validate_mapping(path, iterable, out): elif self.extra != vol.REMOVE_EXTRA: if isinstance(key, str) and key_names: matches = difflib.get_close_matches(key, key_names) - errors.append(ExtraKeysInvalid('extra keys not allowed', key_path, - candidates=matches)) + errors.append( + ExtraKeysInvalid( + "extra keys not allowed", + key_path, + candidates=matches, + ) + ) else: - errors.append(vol.Invalid('extra keys not allowed', key_path)) + errors.append( + vol.Invalid("extra keys not allowed", key_path) + ) # for any required keys left that weren't found and don't have defaults: for key in required_keys: - msg = getattr(key, 'msg', None) or 'required key not provided' + msg = getattr(key, "msg", None) or "required key not provided" errors.append(vol.RequiredFieldInvalid(msg, path + [key])) if errors: raise vol.MultipleInvalid(errors) @@ -179,9 +203,10 @@ def add_extra(self, validator): self._extra_schemas.append(validator) return self + @jschema_extended # pylint: disable=signature-differs def extend(self, *schemas, **kwargs): - extra = kwargs.pop('extra', None) + extra = kwargs.pop("extra", None) if kwargs: raise ValueError if not schemas: diff --git a/esphome/vscode.py b/esphome/vscode.py index 8782ed6e5c84..6e1a0270be7e 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -20,11 +20,11 @@ def _dump_range(range): if range is None: return None return { - 'document': range.start_mark.document, - 'start_line': range.start_mark.line, - 'start_col': range.start_mark.column, - 'end_line': range.end_mark.line, - 'end_col': range.end_mark.column, + "document": range.start_mark.document, + "start_line": range.start_mark.line, + "start_col": range.start_mark.column, + "end_line": range.end_mark.line, + "end_col": range.end_mark.column, } @@ -34,36 +34,42 @@ def __init__(self): self.validation_errors = [] def dump(self): - return json.dumps({ - 'type': 'result', - 'yaml_errors': self.yaml_errors, - 'validation_errors': self.validation_errors, - }) + return json.dumps( + { + "type": "result", + "yaml_errors": self.yaml_errors, + "validation_errors": self.validation_errors, + } + ) def add_yaml_error(self, message): - self.yaml_errors.append({ - 'message': message, - }) + self.yaml_errors.append( + { + "message": message, + } + ) def add_validation_error(self, range_, message): - self.validation_errors.append({ - 'range': _dump_range(range_), - 'message': message, - }) + self.validation_errors.append( + { + "range": _dump_range(range_), + "message": message, + } + ) def read_config(args): while True: CORE.reset() data = json.loads(input()) - assert data['type'] == 'validate' + assert data["type"] == "validate" CORE.vscode = True CORE.ace = args.ace - f = data['file'] + f = data["file"] if CORE.ace: CORE.config_path = os.path.join(args.configuration[0], f) else: - CORE.config_path = data['file'] + CORE.config_path = data["file"] vs = VSCodeResult() try: res = load_config(dict(args.substitution) if args.substitution else {}) diff --git a/esphome/wizard.py b/esphome/wizard.py index 9b6237acf36a..620ceb9b5914 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -7,6 +7,7 @@ import esphome.config_validation as cv from esphome.helpers import color, get_bool_env, write_file + # pylint: disable=anomalous-backslash-in-string from esphome.pins import ESP32_BOARD_PINS, ESP8266_BOARD_PINS from esphome.storage_json import StorageJSON, ext_storage_path @@ -67,22 +68,24 @@ def sanitize_double_quotes(value): - return value.replace('\\', '\\\\').replace('"', '\\"') + return value.replace("\\", "\\\\").replace('"', '\\"') def wizard_file(**kwargs): letters = string.ascii_letters + string.digits - ap_name_base = kwargs['name'].replace('_', ' ').title() + ap_name_base = kwargs["name"].replace("_", " ").title() ap_name = f"{ap_name_base} Fallback Hotspot" if len(ap_name) > 32: ap_name = ap_name_base - kwargs['fallback_name'] = ap_name - kwargs['fallback_psk'] = ''.join(random.choice(letters) for _ in range(12)) + kwargs["fallback_name"] = ap_name + kwargs["fallback_psk"] = "".join(random.choice(letters) for _ in range(12)) config = BASE_CONFIG.format(**kwargs) - if kwargs['password']: - config += ' password: "{0}"\n\nota:\n password: "{0}"\n'.format(kwargs['password']) + if kwargs["password"]: + config += ' password: "{0}"\n\nota:\n password: "{0}"\n'.format( + kwargs["password"] + ) else: config += "\nota:\n" @@ -90,26 +93,29 @@ def wizard_file(**kwargs): def wizard_write(path, **kwargs): - name = kwargs['name'] - board = kwargs['board'] + name = kwargs["name"] + board = kwargs["board"] - kwargs['ssid'] = sanitize_double_quotes(kwargs['ssid']) - kwargs['psk'] = sanitize_double_quotes(kwargs['psk']) - kwargs['password'] = sanitize_double_quotes(kwargs['password']) + kwargs["ssid"] = sanitize_double_quotes(kwargs["ssid"]) + kwargs["psk"] = sanitize_double_quotes(kwargs["psk"]) + kwargs["password"] = sanitize_double_quotes(kwargs["password"]) - if 'platform' not in kwargs: - kwargs['platform'] = 'ESP8266' if board in ESP8266_BOARD_PINS else 'ESP32' - platform = kwargs['platform'] + if "platform" not in kwargs: + kwargs["platform"] = "ESP8266" if board in ESP8266_BOARD_PINS else "ESP32" + platform = kwargs["platform"] write_file(path, wizard_file(**kwargs)) - storage = StorageJSON.from_wizard(name, name + '.local', platform, board) + storage = StorageJSON.from_wizard(name, name + ".local", platform, board) storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) storage.save(storage_path) if get_bool_env(ENV_QUICKWIZARD): + def sleep(time): pass + + else: from time import sleep @@ -131,18 +137,25 @@ def default_input(text, default): # From https://stackoverflow.com/a/518232/8924614 def strip_accents(value): - return ''.join(c for c in unicodedata.normalize('NFD', str(value)) - if unicodedata.category(c) != 'Mn') + return "".join( + c + for c in unicodedata.normalize("NFD", str(value)) + if unicodedata.category(c) != "Mn" + ) def wizard(path): - if not path.endswith('.yaml') and not path.endswith('.yml'): - safe_print("Please make your configuration file {} have the extension .yaml or .yml" - "".format(color('cyan', path))) + if not path.endswith(".yaml") and not path.endswith(".yml"): + safe_print( + "Please make your configuration file {} have the extension .yaml or .yml" + "".format(color("cyan", path)) + ) return 1 if os.path.exists(path): - safe_print("Uh oh, it seems like {} already exists, please delete that file first " - "or chose another configuration file.".format(color('cyan', path))) + safe_print( + "Uh oh, it seems like {} already exists, please delete that file first " + "or chose another configuration file.".format(color("cyan", path)) + ) return 2 safe_print("Hi there!") sleep(1.5) @@ -150,16 +163,25 @@ def wizard(path): sleep(1.25) safe_print("And I'm here to help you get started with ESPHome.") sleep(2.0) - safe_print("In 4 steps I'm going to guide you through creating a basic " - "configuration file for your custom ESP8266/ESP32 firmware. Yay!") + safe_print( + "In 4 steps I'm going to guide you through creating a basic " + "configuration file for your custom ESP8266/ESP32 firmware. Yay!" + ) sleep(3.0) safe_print() safe_print_step(1, CORE_BIG) - safe_print("First up, please choose a " + color('green', 'name') + " for your node.") - safe_print("It should be a unique name that can be used to identify the device later.") + safe_print( + "First up, please choose a " + color("green", "name") + " for your node." + ) + safe_print( + "It should be a unique name that can be used to identify the device later." + ) sleep(1) - safe_print("For example, I like calling the node in my living room {}.".format( - color('bold_white', "livingroom"))) + safe_print( + "For example, I like calling the node in my living room {}.".format( + color("bold_white", "livingroom") + ) + ) safe_print() sleep(1) name = input(color("bold_white", "(name): ")) @@ -169,56 +191,80 @@ def wizard(path): name = cv.valid_name(name) break except vol.Invalid: - safe_print(color("red", f"Oh noes, \"{name}\" isn't a valid name. Names can only " - f"include numbers, lower-case letters, underscores and " - f"hyphens.")) - name = strip_accents(name).lower().replace(' ', '_') - name = ''.join(c for c in name if c in ALLOWED_NAME_CHARS) - safe_print("Shall I use \"{}\" as the name instead?".format(color('cyan', name))) + safe_print( + color( + "red", + f'Oh noes, "{name}" isn\'t a valid name. Names can only ' + f"include numbers, lower-case letters, underscores and " + f"hyphens.", + ) + ) + name = strip_accents(name).lower().replace(" ", "_") + name = "".join(c for c in name if c in ALLOWED_NAME_CHARS) + safe_print( + 'Shall I use "{}" as the name instead?'.format(color("cyan", name)) + ) sleep(0.5) name = default_input("(name [{}]): ", name) - safe_print("Great! Your node is now called \"{}\".".format(color('cyan', name))) + safe_print('Great! Your node is now called "{}".'.format(color("cyan", name))) sleep(1) safe_print_step(2, ESP_BIG) - safe_print("Now I'd like to know what microcontroller you're using so that I can compile " - "firmwares for it.") - safe_print("Are you using an " + color('green', 'ESP32') + " or " + - color('green', 'ESP8266') + " platform? (Choose ESP8266 for Sonoff devices)") + safe_print( + "Now I'd like to know what microcontroller you're using so that I can compile " + "firmwares for it." + ) + safe_print( + "Are you using an " + + color("green", "ESP32") + + " or " + + color("green", "ESP8266") + + " platform? (Choose ESP8266 for Sonoff devices)" + ) while True: sleep(0.5) safe_print() safe_print("Please enter either ESP32 or ESP8266.") platform = input(color("bold_white", "(ESP32/ESP8266): ")) try: - platform = vol.All(vol.Upper, vol.Any('ESP32', 'ESP8266'))(platform) + platform = vol.All(vol.Upper, vol.Any("ESP32", "ESP8266"))(platform) break except vol.Invalid: - safe_print("Unfortunately, I can't find an espressif microcontroller called " - "\"{}\". Please try again.".format(platform)) - safe_print("Thanks! You've chosen {} as your platform.".format(color('cyan', platform))) + safe_print( + "Unfortunately, I can't find an espressif microcontroller called " + '"{}". Please try again.'.format(platform) + ) + safe_print( + "Thanks! You've chosen {} as your platform.".format(color("cyan", platform)) + ) safe_print() sleep(1) - if platform == 'ESP32': - board_link = 'http://docs.platformio.org/en/latest/platforms/espressif32.html#boards' + if platform == "ESP32": + board_link = ( + "http://docs.platformio.org/en/latest/platforms/espressif32.html#boards" + ) else: - board_link = 'http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards' + board_link = ( + "http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards" + ) - safe_print("Next, I need to know what " + color('green', 'board') + " you're using.") + safe_print( + "Next, I need to know what " + color("green", "board") + " you're using." + ) sleep(0.5) - safe_print("Please go to {} and choose a board.".format(color('green', board_link))) - if platform == 'ESP32': - safe_print("(Type " + color('green', 'esp01_1m') + " for Sonoff devices)") + safe_print("Please go to {} and choose a board.".format(color("green", board_link))) + if platform == "ESP32": + safe_print("(Type " + color("green", "esp01_1m") + " for Sonoff devices)") safe_print() # Don't sleep because user needs to copy link - if platform == 'ESP32': - safe_print("For example \"{}\".".format(color("bold_white", 'nodemcu-32s'))) + if platform == "ESP32": + safe_print('For example "{}".'.format(color("bold_white", "nodemcu-32s"))) boards = list(ESP32_BOARD_PINS.keys()) else: - safe_print("For example \"{}\".".format(color("bold_white", 'nodemcuv2'))) + safe_print('For example "{}".'.format(color("bold_white", "nodemcuv2"))) boards = list(ESP8266_BOARD_PINS.keys()) - safe_print("Options: {}".format(', '.join(sorted(boards)))) + safe_print("Options: {}".format(", ".join(sorted(boards)))) while True: board = input(color("bold_white", "(board): ")) @@ -226,69 +272,102 @@ def wizard(path): board = vol.All(vol.Lower, vol.Any(*boards))(board) break except vol.Invalid: - safe_print(color('red', f"Sorry, I don't think the board \"{board}\" exists.")) + safe_print( + color("red", f'Sorry, I don\'t think the board "{board}" exists.') + ) safe_print() sleep(0.25) safe_print() - safe_print("Way to go! You've chosen {} as your board.".format(color('cyan', board))) + safe_print( + "Way to go! You've chosen {} as your board.".format(color("cyan", board)) + ) safe_print() sleep(1) safe_print_step(3, WIFI_BIG) - safe_print("In this step, I'm going to create the configuration for " - "WiFi.") + safe_print("In this step, I'm going to create the configuration for " "WiFi.") safe_print() sleep(1) - safe_print("First, what's the " + color('green', 'SSID') + - f" (the name) of the WiFi network {name} I should connect to?") + safe_print( + "First, what's the " + + color("green", "SSID") + + f" (the name) of the WiFi network {name} I should connect to?" + ) sleep(1.5) - safe_print("For example \"{}\".".format(color('bold_white', "Abraham Linksys"))) + safe_print('For example "{}".'.format(color("bold_white", "Abraham Linksys"))) while True: - ssid = input(color('bold_white', "(ssid): ")) + ssid = input(color("bold_white", "(ssid): ")) try: ssid = cv.ssid(ssid) break except vol.Invalid: - safe_print(color('red', "Unfortunately, \"{}\" doesn't seem to be a valid SSID. " - "Please try again.".format(ssid))) + safe_print( + color( + "red", + 'Unfortunately, "{}" doesn\'t seem to be a valid SSID. ' + "Please try again.".format(ssid), + ) + ) safe_print() sleep(1) - safe_print("Thank you very much! You've just chosen \"{}\" as your SSID." - "".format(color('cyan', ssid))) + safe_print( + 'Thank you very much! You\'ve just chosen "{}" as your SSID.' + "".format(color("cyan", ssid)) + ) safe_print() sleep(0.75) - safe_print("Now please state the " + color('green', 'password') + - " of the WiFi network so that I can connect to it (Leave empty for no password)") + safe_print( + "Now please state the " + + color("green", "password") + + " of the WiFi network so that I can connect to it (Leave empty for no password)" + ) safe_print() - safe_print("For example \"{}\"".format(color('bold_white', 'PASSWORD42'))) + safe_print('For example "{}"'.format(color("bold_white", "PASSWORD42"))) sleep(0.5) - psk = input(color('bold_white', '(PSK): ')) - safe_print("Perfect! WiFi is now set up (you can create static IPs and so on later).") + psk = input(color("bold_white", "(PSK): ")) + safe_print( + "Perfect! WiFi is now set up (you can create static IPs and so on later)." + ) sleep(1.5) safe_print_step(4, OTA_BIG) - safe_print("Almost there! ESPHome can automatically upload custom firmwares over WiFi " - "(over the air) and integrates into Home Assistant with a native API.") - safe_print("This can be insecure if you do not trust the WiFi network. Do you want to set " - "a " + color('green', 'password') + " for connecting to this ESP?") + safe_print( + "Almost there! ESPHome can automatically upload custom firmwares over WiFi " + "(over the air) and integrates into Home Assistant with a native API." + ) + safe_print( + "This can be insecure if you do not trust the WiFi network. Do you want to set " + "a " + color("green", "password") + " for connecting to this ESP?" + ) safe_print() sleep(0.25) safe_print("Press ENTER for no password") - password = input(color('bold_white', '(password): ')) - - wizard_write(path=path, name=name, platform=platform, board=board, - ssid=ssid, psk=psk, password=password) + password = input(color("bold_white", "(password): ")) + + wizard_write( + path=path, + name=name, + platform=platform, + board=board, + ssid=ssid, + psk=psk, + password=password, + ) safe_print() - safe_print(color('cyan', "DONE! I've now written a new configuration file to ") + - color('bold_cyan', path)) + safe_print( + color("cyan", "DONE! I've now written a new configuration file to ") + + color("bold_cyan", path) + ) safe_print() safe_print("Next steps:") - safe_print(" > Check your Home Assistant \"integrations\" screen. If all goes well, you " - "should see your ESP being discovered automatically.") + safe_print( + ' > Check your Home Assistant "integrations" screen. If all goes well, you ' + "should see your ESP being discovered automatically." + ) safe_print(" > Then follow the rest of the getting started guide:") safe_print(" > https://esphome.io/guides/getting_started_command_line.html") return 0 diff --git a/esphome/writer.py b/esphome/writer.py index 237f0fb4b7e8..ec772b512710 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -3,30 +3,46 @@ import re from esphome.config import iter_components -from esphome.const import CONF_BOARD_FLASH_MODE, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS, \ - HEADER_FILE_EXTENSIONS, SOURCE_FILE_EXTENSIONS, __version__, ARDUINO_VERSION_ESP8266, \ - ENV_NOGITIGNORE +from esphome.const import ( + CONF_BOARD_FLASH_MODE, + CONF_ESPHOME, + CONF_PLATFORMIO_OPTIONS, + HEADER_FILE_EXTENSIONS, + SOURCE_FILE_EXTENSIONS, + __version__, + ARDUINO_VERSION_ESP8266, + ENV_NOGITIGNORE, +) from esphome.core import CORE, EsphomeError -from esphome.helpers import mkdir_p, read_file, write_file_if_changed, walk_files, \ - copy_file_if_changed, get_bool_env +from esphome.helpers import ( + mkdir_p, + read_file, + write_file_if_changed, + walk_files, + copy_file_if_changed, + get_bool_env, +) from esphome.storage_json import StorageJSON, storage_path from esphome.pins import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS _LOGGER = logging.getLogger(__name__) -CPP_AUTO_GENERATE_BEGIN = '// ========== AUTO GENERATED CODE BEGIN ===========' -CPP_AUTO_GENERATE_END = '// =========== AUTO GENERATED CODE END ============' -CPP_INCLUDE_BEGIN = '// ========== AUTO GENERATED INCLUDE BLOCK BEGIN ===========' -CPP_INCLUDE_END = '// ========== AUTO GENERATED INCLUDE BLOCK END ===========' -INI_AUTO_GENERATE_BEGIN = '; ========== AUTO GENERATED CODE BEGIN ===========' -INI_AUTO_GENERATE_END = '; =========== AUTO GENERATED CODE END ============' +CPP_AUTO_GENERATE_BEGIN = "// ========== AUTO GENERATED CODE BEGIN ===========" +CPP_AUTO_GENERATE_END = "// =========== AUTO GENERATED CODE END ============" +CPP_INCLUDE_BEGIN = "// ========== AUTO GENERATED INCLUDE BLOCK BEGIN ===========" +CPP_INCLUDE_END = "// ========== AUTO GENERATED INCLUDE BLOCK END ===========" +INI_AUTO_GENERATE_BEGIN = "; ========== AUTO GENERATED CODE BEGIN ===========" +INI_AUTO_GENERATE_END = "; =========== AUTO GENERATED CODE END ============" -CPP_BASE_FORMAT = ("""// Auto generated code by esphome -""", """" +CPP_BASE_FORMAT = ( + """// Auto generated code by esphome +""", + """" void setup() { // ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== - """, """ + """, + """ // ========= YOU CAN EDIT AFTER THIS LINE ========= App.setup(); } @@ -34,9 +50,11 @@ void loop() { App.loop(); } -""") +""", +) -INI_BASE_FORMAT = ("""; Auto generated code by esphome +INI_BASE_FORMAT = ( + """; Auto generated code by esphome [common] lib_deps = @@ -44,13 +62,15 @@ upload_flags = ; ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== -""", """ +""", + """ ; ========= YOU CAN EDIT AFTER THIS LINE ========= -""") +""", +) UPLOAD_SPEED_OVERRIDE = { - 'esp210': 57600, + "esp210": 57600, } @@ -62,10 +82,9 @@ def get_flags(key): def get_include_text(): - include_text = '#include "esphome.h"\n' \ - 'using namespace esphome;\n' + include_text = '#include "esphome.h"\nusing namespace esphome;\n' for _, component, conf in iter_components(CORE.config): - if not hasattr(component, 'includes'): + if not hasattr(component, "includes"): continue includes = component.includes if callable(includes): @@ -73,10 +92,10 @@ def get_include_text(): if includes is None: continue if isinstance(includes, list): - includes = '\n'.join(includes) + includes = "\n".join(includes) if not includes: continue - include_text += includes + '\n' + include_text += includes + "\n" return include_text @@ -86,7 +105,7 @@ def replace_file_content(text, pattern, repl): def migrate_src_version_0_to_1(): - main_cpp = CORE.relative_build_path('src', 'main.cpp') + main_cpp = CORE.relative_build_path("src", "main.cpp") if not os.path.isfile(main_cpp): return @@ -95,22 +114,35 @@ def migrate_src_version_0_to_1(): if CPP_INCLUDE_BEGIN in content: return - content, count = replace_file_content(content, r'\s*delay\((?:16|20)\);', '') + content, count = replace_file_content(content, r"\s*delay\((?:16|20)\);", "") if count != 0: - _LOGGER.info("Migration: Removed %s occurrence of 'delay(16);' in %s", count, main_cpp) + _LOGGER.info( + "Migration: Removed %s occurrence of 'delay(16);' in %s", count, main_cpp + ) - content, count = replace_file_content(content, r'using namespace esphomelib;', '') + content, count = replace_file_content(content, r"using namespace esphomelib;", "") if count != 0: - _LOGGER.info("Migration: Removed %s occurrence of 'using namespace esphomelib;' " - "in %s", count, main_cpp) + _LOGGER.info( + "Migration: Removed %s occurrence of 'using namespace esphomelib;' " + "in %s", + count, + main_cpp, + ) if CPP_INCLUDE_BEGIN not in content: - content, count = replace_file_content(content, r'#include "esphomelib/application.h"', - CPP_INCLUDE_BEGIN + '\n' + CPP_INCLUDE_END) + content, count = replace_file_content( + content, + r'#include "esphomelib/application.h"', + CPP_INCLUDE_BEGIN + "\n" + CPP_INCLUDE_END, + ) if count == 0: - _LOGGER.error("Migration failed. ESPHome 1.10.0 needs to have a new auto-generated " - "include section in the %s file. Please remove %s and let it be " - "auto-generated again.", main_cpp, main_cpp) + _LOGGER.error( + "Migration failed. ESPHome 1.10.0 needs to have a new auto-generated " + "include section in the %s file. Please remove %s and let it be " + "auto-generated again.", + main_cpp, + main_cpp, + ) _LOGGER.info("Migration: Added include section to %s", main_cpp) write_file_if_changed(main_cpp, content) @@ -160,14 +192,14 @@ def update_storage_json(): def format_ini(data): - content = '' + content = "" for key, value in sorted(data.items()): if isinstance(value, (list, set, tuple)): - content += f'{key} =\n' + content += f"{key} =\n" for x in value: - content += f' {x}\n' + content += f" {x}\n" else: - content += f'{key} = {value}\n' + content += f"{key} = {value}\n" return content @@ -197,23 +229,23 @@ def get_ini_content(): build_flags = gather_build_flags() data = { - 'platform': CORE.arduino_version, - 'board': CORE.board, - 'framework': 'arduino', - 'lib_deps': lib_deps + ['${common.lib_deps}'], - 'build_flags': build_flags + ['${common.build_flags}'], - 'upload_speed': UPLOAD_SPEED_OVERRIDE.get(CORE.board, 115200), + "platform": CORE.arduino_version, + "board": CORE.board, + "framework": "arduino", + "lib_deps": lib_deps + ["${common.lib_deps}"], + "build_flags": build_flags + ["${common.build_flags}"], + "upload_speed": UPLOAD_SPEED_OVERRIDE.get(CORE.board, 115200), } if CORE.is_esp32: - data['board_build.partitions'] = "partitions.csv" - partitions_csv = CORE.relative_build_path('partitions.csv') + data["board_build.partitions"] = "partitions.csv" + partitions_csv = CORE.relative_build_path("partitions.csv") write_file_if_changed(partitions_csv, ESP32_LARGE_PARTITIONS_CSV) # pylint: disable=unsubscriptable-object if CONF_BOARD_FLASH_MODE in CORE.config[CONF_ESPHOME]: flash_mode = CORE.config[CONF_ESPHOME][CONF_BOARD_FLASH_MODE] - data['board_build.flash_mode'] = flash_mode + data["board_build.flash_mode"] = flash_mode # Build flags if CORE.is_esp8266 and CORE.board in ESP8266_FLASH_SIZES: @@ -221,11 +253,11 @@ def get_ini_content(): ld_scripts = ESP8266_LD_SCRIPTS[flash_size] versions_with_old_ldscripts = [ - ARDUINO_VERSION_ESP8266['2.4.0'], - ARDUINO_VERSION_ESP8266['2.4.1'], - ARDUINO_VERSION_ESP8266['2.4.2'], + ARDUINO_VERSION_ESP8266["2.4.0"], + ARDUINO_VERSION_ESP8266["2.4.1"], + ARDUINO_VERSION_ESP8266["2.4.2"], ] - if CORE.arduino_version == ARDUINO_VERSION_ESP8266['2.3.0']: + if CORE.arduino_version == ARDUINO_VERSION_ESP8266["2.3.0"]: # No ld script support ld_script = None if CORE.arduino_version in versions_with_old_ldscripts: @@ -235,14 +267,14 @@ def get_ini_content(): ld_script = ld_scripts[1] if ld_script is not None: - data['board_build.ldscript'] = ld_script + data["board_build.ldscript"] = ld_script # Ignore libraries that are not explicitly used, but may # be added by LDF # data['lib_ldf_mode'] = 'chain' data.update(CORE.config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS, {})) - content = f'[env:{CORE.name}]\n' + content = f"[env:{CORE.name}]\n" content += format_ini(data) return content @@ -251,32 +283,42 @@ def get_ini_content(): def find_begin_end(text, begin_s, end_s): begin_index = text.find(begin_s) if begin_index == -1: - raise EsphomeError("Could not find auto generated code begin in file, either " - "delete the main sketch file or insert the comment again.") + raise EsphomeError( + "Could not find auto generated code begin in file, either " + "delete the main sketch file or insert the comment again." + ) if text.find(begin_s, begin_index + 1) != -1: - raise EsphomeError("Found multiple auto generate code begins, don't know " - "which to chose, please remove one of them.") + raise EsphomeError( + "Found multiple auto generate code begins, don't know " + "which to chose, please remove one of them." + ) end_index = text.find(end_s) if end_index == -1: - raise EsphomeError("Could not find auto generated code end in file, either " - "delete the main sketch file or insert the comment again.") + raise EsphomeError( + "Could not find auto generated code end in file, either " + "delete the main sketch file or insert the comment again." + ) if text.find(end_s, end_index + 1) != -1: - raise EsphomeError("Found multiple auto generate code endings, don't know " - "which to chose, please remove one of them.") + raise EsphomeError( + "Found multiple auto generate code endings, don't know " + "which to chose, please remove one of them." + ) - return text[:begin_index], text[(end_index + len(end_s)):] + return text[:begin_index], text[(end_index + len(end_s)) :] def write_platformio_ini(content): update_storage_json() - path = CORE.relative_build_path('platformio.ini') + path = CORE.relative_build_path("platformio.ini") if os.path.isfile(path): text = read_file(path) - content_format = find_begin_end(text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END) + content_format = find_begin_end( + text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END + ) else: content_format = INI_BASE_FORMAT - full_file = content_format[0] + INI_AUTO_GENERATE_BEGIN + '\n' + content + full_file = content_format[0] + INI_AUTO_GENERATE_BEGIN + "\n" + content full_file += INI_AUTO_GENERATE_END + content_format[1] write_file_if_changed(path, full_file) @@ -298,8 +340,8 @@ def write_platformio_project(): #pragma once #define ESPHOME_VERSION "{}" """ -DEFINES_H_TARGET = 'esphome/core/defines.h' -VERSION_H_TARGET = 'esphome/core/version.h' +DEFINES_H_TARGET = "esphome/core/defines.h" +VERSION_H_TARGET = "esphome/core/version.h" ESPHOME_README_TXT = """ THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY @@ -326,18 +368,20 @@ def copy_src_tree(): for target, path in source_files_l: if os.path.splitext(path)[1] in HEADER_FILE_EXTENSIONS: include_l.append(f'#include "{target}"') - include_l.append('') - include_s = '\n'.join(include_l) + include_l.append("") + include_s = "\n".join(include_l) source_files_copy = source_files.copy() source_files_copy.pop(DEFINES_H_TARGET) - for path in walk_files(CORE.relative_src_path('esphome')): + for path in walk_files(CORE.relative_src_path("esphome")): if os.path.splitext(path)[1] not in SOURCE_FILE_EXTENSIONS: # Not a source file, ignore continue # Transform path to target path name - target = os.path.relpath(path, CORE.relative_src_path()).replace(os.path.sep, '/') + target = os.path.relpath(path, CORE.relative_src_path()).replace( + os.path.sep, "/" + ) if target in (DEFINES_H_TARGET, VERSION_H_TARGET): # Ignore defines.h, will be dealt with later continue @@ -350,32 +394,41 @@ def copy_src_tree(): # Now copy new files for target, src_path in source_files_copy.items(): - dst_path = CORE.relative_src_path(*target.split('/')) + dst_path = CORE.relative_src_path(*target.split("/")) copy_file_if_changed(src_path, dst_path) # Finally copy defines - write_file_if_changed(CORE.relative_src_path('esphome', 'core', 'defines.h'), - generate_defines_h()) - write_file_if_changed(CORE.relative_src_path('esphome', 'README.txt'), - ESPHOME_README_TXT) - write_file_if_changed(CORE.relative_src_path('esphome.h'), - ESPHOME_H_FORMAT.format(include_s)) - write_file_if_changed(CORE.relative_src_path('esphome', 'core', 'version.h'), - VERSION_H_FORMAT.format(__version__)) + write_file_if_changed( + CORE.relative_src_path("esphome", "core", "defines.h"), generate_defines_h() + ) + write_file_if_changed( + CORE.relative_src_path("esphome", "README.txt"), ESPHOME_README_TXT + ) + write_file_if_changed( + CORE.relative_src_path("esphome.h"), ESPHOME_H_FORMAT.format(include_s) + ) + write_file_if_changed( + CORE.relative_src_path("esphome", "core", "version.h"), + VERSION_H_FORMAT.format(__version__), + ) def generate_defines_h(): define_content_l = [x.as_macro for x in CORE.defines] define_content_l.sort() - return DEFINES_H_FORMAT.format('\n'.join(define_content_l)) + return DEFINES_H_FORMAT.format("\n".join(define_content_l)) def write_cpp(code_s): - path = CORE.relative_src_path('main.cpp') + path = CORE.relative_src_path("main.cpp") if os.path.isfile(path): text = read_file(path) - code_format = find_begin_end(text, CPP_AUTO_GENERATE_BEGIN, CPP_AUTO_GENERATE_END) - code_format_ = find_begin_end(code_format[0], CPP_INCLUDE_BEGIN, CPP_INCLUDE_END) + code_format = find_begin_end( + text, CPP_AUTO_GENERATE_BEGIN, CPP_AUTO_GENERATE_END + ) + code_format_ = find_begin_end( + code_format[0], CPP_INCLUDE_BEGIN, CPP_INCLUDE_END + ) code_format = (code_format_[0], code_format_[1], code_format[1]) else: code_format = CPP_BASE_FORMAT @@ -384,8 +437,10 @@ def write_cpp(code_s): global_s = '#include "esphome.h"\n' global_s += CORE.cpp_global_section - full_file = code_format[0] + CPP_INCLUDE_BEGIN + '\n' + global_s + CPP_INCLUDE_END - full_file += code_format[1] + CPP_AUTO_GENERATE_BEGIN + '\n' + code_s + CPP_AUTO_GENERATE_END + full_file = code_format[0] + CPP_INCLUDE_BEGIN + "\n" + global_s + CPP_INCLUDE_END + full_file += ( + code_format[1] + CPP_AUTO_GENERATE_BEGIN + "\n" + code_s + CPP_AUTO_GENERATE_END + ) full_file += code_format[2] write_file_if_changed(path, full_file) @@ -417,7 +472,7 @@ def clean_build(): def write_gitignore(): - path = CORE.relative_config_path('.gitignore') + path = CORE.relative_config_path(".gitignore") if not os.path.isfile(path): - with open(path, 'w') as f: + with open(path, "w") as f: f.write(GITIGNORE_CONTENT) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index 857a986538e1..f98bb272b817 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -11,7 +11,14 @@ from esphome import core from esphome.config_helpers import read_config_file -from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange +from esphome.core import ( + EsphomeError, + IPAddress, + Lambda, + MACAddress, + TimePeriod, + DocumentRange, +) from esphome.helpers import add_class_to_obj from esphome.util import OrderedDict, filter_yaml_files @@ -20,7 +27,7 @@ # Mostly copied from Home Assistant because that code works fine and # let's not reinvent the wheel here -SECRET_YAML = 'secrets.yaml' +SECRET_YAML = "secrets.yaml" _SECRET_CACHE = {} _SECRET_VALUES = {} @@ -28,20 +35,35 @@ class ESPHomeDataBase: @property def esp_range(self): - return getattr(self, '_esp_range', None) + return getattr(self, "_esp_range", None) + + @property + def content_offset(self): + return getattr(self, "_content_offset", 0) def from_node(self, node): # pylint: disable=attribute-defined-outside-init self._esp_range = DocumentRange.from_marks(node.start_mark, node.end_mark) + if isinstance(node, yaml.ScalarNode): + if node.style is not None and node.style in "|>": + self._content_offset = 1 + + def from_database(self, database): + # pylint: disable=attribute-defined-outside-init + self._esp_range = database.esp_range + self._content_offset = database.content_offset class ESPForceValue: pass -def make_data_base(value): +def make_data_base(value, from_database: ESPHomeDataBase = None): try: - return add_class_to_obj(value, ESPHomeDataBase) + value = add_class_to_obj(value, ESPHomeDataBase) + if from_database is not None: + value.from_database(from_database) + return value except TypeError: # Adding class failed, ignore error return value @@ -109,13 +131,13 @@ def construct_yaml_map(self, node): for key_node, value_node in node.value: # merge key is '<<' - is_merge_key = key_node.tag == 'tag:yaml.org,2002:merge' + is_merge_key = key_node.tag == "tag:yaml.org,2002:merge" # key has no explicit tag set - is_default_tag = key_node.tag == 'tag:yaml.org,2002:value' + is_default_tag = key_node.tag == "tag:yaml.org,2002:value" if is_default_tag: # Default tag for mapping keys is string - key_node.tag = 'tag:yaml.org,2002:str' + key_node.tag = "tag:yaml.org,2002:str" if not is_merge_key: # base case, this is a simple key-value pair @@ -128,13 +150,19 @@ def construct_yaml_map(self, node): except TypeError: # pylint: disable=raise-missing-from raise yaml.constructor.ConstructorError( - f'Invalid key "{key}" (not hashable)', key_node.start_mark) + f'Invalid key "{key}" (not hashable)', key_node.start_mark + ) + + key = make_data_base(str(key)) + key.from_node(key_node) # Check if it is a duplicate key if key in seen_keys: raise yaml.constructor.ConstructorError( - f'Duplicate key "{key}"', key_node.start_mark, - 'NOTE: Previous declaration here:', seen_keys[key], + f'Duplicate key "{key}"', + key_node.start_mark, + "NOTE: Previous declaration here:", + seen_keys[key], ) seen_keys[key] = key_node.start_mark @@ -153,15 +181,22 @@ def construct_yaml_map(self, node): for item in value: if not isinstance(item, dict): raise yaml.constructor.ConstructorError( - "While constructing a mapping", node.start_mark, - "Expected a mapping for merging, but found {}".format(type(item)), - value_node.start_mark) + "While constructing a mapping", + node.start_mark, + "Expected a mapping for merging, but found {}".format( + type(item) + ), + value_node.start_mark, + ) merge_pairs.extend(item.items()) else: raise yaml.constructor.ConstructorError( - "While constructing a mapping", node.start_mark, + "While constructing a mapping", + node.start_mark, "Expected a mapping or list of mappings for merging, " - "but found {}".format(type(value)), value_node.start_mark) + "but found {}".format(type(value)), + value_node.start_mark, + ) if merge_pairs: # We found some merge keys along the way, merge them into base pairs @@ -192,7 +227,7 @@ def construct_env_var(self, node): args = node.value.split() # Check for a default value if len(args) > 1: - return os.getenv(args[0], ' '.join(args[1:])) + return os.getenv(args[0], " ".join(args[1:])) if args[0] in os.environ: return os.environ[args[0]] raise yaml.MarkedYAMLError( @@ -223,12 +258,12 @@ def construct_include(self, node): @_add_data_ref def construct_include_dir_list(self, node): - files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml')) + files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml")) return [_load_yaml_internal(f) for f in files] @_add_data_ref def construct_include_dir_merge_list(self, node): - files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml')) + files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml")) merged_list = [] for fname in files: loaded_yaml = _load_yaml_internal(fname) @@ -238,7 +273,7 @@ def construct_include_dir_merge_list(self, node): @_add_data_ref def construct_include_dir_named(self, node): - files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml')) + files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml")) mapping = OrderedDict() for fname in files: filename = os.path.splitext(os.path.basename(fname))[0] @@ -247,7 +282,7 @@ def construct_include_dir_named(self, node): @_add_data_ref def construct_include_dir_merge_named(self, node): - files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml')) + files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml")) mapping = OrderedDict() for fname in files: loaded_yaml = _load_yaml_internal(fname) @@ -265,24 +300,36 @@ def construct_force(self, node): return add_class_to_obj(obj, ESPForceValue) -ESPHomeLoader.add_constructor('tag:yaml.org,2002:int', ESPHomeLoader.construct_yaml_int) -ESPHomeLoader.add_constructor('tag:yaml.org,2002:float', ESPHomeLoader.construct_yaml_float) -ESPHomeLoader.add_constructor('tag:yaml.org,2002:binary', ESPHomeLoader.construct_yaml_binary) -ESPHomeLoader.add_constructor('tag:yaml.org,2002:omap', ESPHomeLoader.construct_yaml_omap) -ESPHomeLoader.add_constructor('tag:yaml.org,2002:str', ESPHomeLoader.construct_yaml_str) -ESPHomeLoader.add_constructor('tag:yaml.org,2002:seq', ESPHomeLoader.construct_yaml_seq) -ESPHomeLoader.add_constructor('tag:yaml.org,2002:map', ESPHomeLoader.construct_yaml_map) -ESPHomeLoader.add_constructor('!env_var', ESPHomeLoader.construct_env_var) -ESPHomeLoader.add_constructor('!secret', ESPHomeLoader.construct_secret) -ESPHomeLoader.add_constructor('!include', ESPHomeLoader.construct_include) -ESPHomeLoader.add_constructor('!include_dir_list', ESPHomeLoader.construct_include_dir_list) -ESPHomeLoader.add_constructor('!include_dir_merge_list', - ESPHomeLoader.construct_include_dir_merge_list) -ESPHomeLoader.add_constructor('!include_dir_named', ESPHomeLoader.construct_include_dir_named) -ESPHomeLoader.add_constructor('!include_dir_merge_named', - ESPHomeLoader.construct_include_dir_merge_named) -ESPHomeLoader.add_constructor('!lambda', ESPHomeLoader.construct_lambda) -ESPHomeLoader.add_constructor('!force', ESPHomeLoader.construct_force) +ESPHomeLoader.add_constructor("tag:yaml.org,2002:int", ESPHomeLoader.construct_yaml_int) +ESPHomeLoader.add_constructor( + "tag:yaml.org,2002:float", ESPHomeLoader.construct_yaml_float +) +ESPHomeLoader.add_constructor( + "tag:yaml.org,2002:binary", ESPHomeLoader.construct_yaml_binary +) +ESPHomeLoader.add_constructor( + "tag:yaml.org,2002:omap", ESPHomeLoader.construct_yaml_omap +) +ESPHomeLoader.add_constructor("tag:yaml.org,2002:str", ESPHomeLoader.construct_yaml_str) +ESPHomeLoader.add_constructor("tag:yaml.org,2002:seq", ESPHomeLoader.construct_yaml_seq) +ESPHomeLoader.add_constructor("tag:yaml.org,2002:map", ESPHomeLoader.construct_yaml_map) +ESPHomeLoader.add_constructor("!env_var", ESPHomeLoader.construct_env_var) +ESPHomeLoader.add_constructor("!secret", ESPHomeLoader.construct_secret) +ESPHomeLoader.add_constructor("!include", ESPHomeLoader.construct_include) +ESPHomeLoader.add_constructor( + "!include_dir_list", ESPHomeLoader.construct_include_dir_list +) +ESPHomeLoader.add_constructor( + "!include_dir_merge_list", ESPHomeLoader.construct_include_dir_merge_list +) +ESPHomeLoader.add_constructor( + "!include_dir_named", ESPHomeLoader.construct_include_dir_named +) +ESPHomeLoader.add_constructor( + "!include_dir_merge_named", ESPHomeLoader.construct_include_dir_merge_named +) +ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda) +ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force) def load_yaml(fname): @@ -305,13 +352,14 @@ def _load_yaml_internal(fname): def dump(dict_): """Dump YAML to a string and remove null.""" - return yaml.dump(dict_, default_flow_style=False, allow_unicode=True, - Dumper=ESPHomeDumper) + return yaml.dump( + dict_, default_flow_style=False, allow_unicode=True, Dumper=ESPHomeDumper + ) def _is_file_valid(name): """Decide if a file is valid.""" - return not name.startswith('.') + return not name.startswith(".") def _find_files(directory, pattern): @@ -338,7 +386,7 @@ def represent_mapping(self, tag, mapping, flow_style=None): if self.alias_key is not None: self.represented_objects[self.alias_key] = node best_style = True - if hasattr(mapping, 'items'): + if hasattr(mapping, "items"): mapping = list(mapping.items()) for item_key, item_value in mapping: node_key = self.represent_data(item_key) @@ -356,29 +404,31 @@ def represent_mapping(self, tag, mapping, flow_style=None): return node def represent_secret(self, value): - return self.represent_scalar(tag='!secret', value=_SECRET_VALUES[str(value)]) + return self.represent_scalar(tag="!secret", value=_SECRET_VALUES[str(value)]) def represent_stringify(self, value): if is_secret(value): return self.represent_secret(value) - return self.represent_scalar(tag='tag:yaml.org,2002:str', value=str(value)) + return self.represent_scalar(tag="tag:yaml.org,2002:str", value=str(value)) # pylint: disable=arguments-differ def represent_bool(self, value): - return self.represent_scalar('tag:yaml.org,2002:bool', 'true' if value else 'false') + return self.represent_scalar( + "tag:yaml.org,2002:bool", "true" if value else "false" + ) def represent_int(self, value): if is_secret(value): return self.represent_secret(value) - return self.represent_scalar(tag='tag:yaml.org,2002:int', value=str(value)) + return self.represent_scalar(tag="tag:yaml.org,2002:int", value=str(value)) def represent_float(self, value): if is_secret(value): return self.represent_secret(value) if math.isnan(value): - value = '.nan' + value = ".nan" elif math.isinf(value): - value = '.inf' if value > 0 else '-.inf' + value = ".inf" if value > 0 else "-.inf" else: value = str(repr(value)).lower() # Note that in some cases `repr(data)` represents a float number @@ -388,14 +438,14 @@ def represent_float(self, value): # Unfortunately, this is not a valid float representation according # to the definition of the `!!float` tag. We fix this by adding # '.0' before the 'e' symbol. - if '.' not in value and 'e' in value: - value = value.replace('e', '.0e', 1) - return self.represent_scalar(tag='tag:yaml.org,2002:float', value=value) + if "." not in value and "e" in value: + value = value.replace("e", ".0e", 1) + return self.represent_scalar(tag="tag:yaml.org,2002:float", value=value) def represent_lambda(self, value): if is_secret(value.value): return self.represent_secret(value.value) - return self.represent_scalar(tag='!lambda', value=value.value, style='|') + return self.represent_scalar(tag="!lambda", value=value.value, style="|") def represent_id(self, value): if is_secret(value.id): @@ -404,12 +454,11 @@ def represent_id(self, value): ESPHomeDumper.add_multi_representer( - dict, - lambda dumper, value: dumper.represent_mapping('tag:yaml.org,2002:map', value) + dict, lambda dumper, value: dumper.represent_mapping("tag:yaml.org,2002:map", value) ) ESPHomeDumper.add_multi_representer( list, - lambda dumper, value: dumper.represent_sequence('tag:yaml.org,2002:seq', value) + lambda dumper, value: dumper.represent_sequence("tag:yaml.org,2002:seq", value), ) ESPHomeDumper.add_multi_representer(bool, ESPHomeDumper.represent_bool) ESPHomeDumper.add_multi_representer(str, ESPHomeDumper.represent_stringify) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index a8ca5b3c53a3..a44c7c9114d0 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -20,7 +20,7 @@ # Some DNS constants -_MDNS_ADDR = '224.0.0.251' +_MDNS_ADDR = "224.0.0.251" _MDNS_PORT = 5353 _MAX_MSG_ABSOLUTE = 8966 @@ -96,7 +96,7 @@ def log_exception_warning(cls, logger_data=None): logger = log.debug if logger_data is not None: logger(*logger_data) - logger('Exception occurred:', exc_info=True) + logger("Exception occurred:", exc_info=True) @classmethod def log_warning_once(cls, *args): @@ -129,9 +129,11 @@ def __init__(self, name, type_, class_): def answered_by(self, rec): """Returns true if the question is answered by the record""" - return (self.class_ == rec.class_ and - (self.type == rec.type or self.type == _TYPE_ANY) and - self.name == rec.name) + return ( + self.class_ == rec.class_ + and (self.type == rec.type or self.type == _TYPE_ANY) + and self.name == rec.name + ) class DNSRecord(DNSEntry): @@ -202,26 +204,32 @@ def __init__(self, data): self.valid = True except (IndexError, struct.error, IncomingDecodeError): - self.log_exception_warning(( - 'Choked at offset %d while unpacking %r', self.offset, data)) + self.log_exception_warning( + ("Choked at offset %d while unpacking %r", self.offset, data) + ) def unpack(self, format_): length = struct.calcsize(format_) - info = struct.unpack( - format_, self.data[self.offset:self.offset + length]) + info = struct.unpack(format_, self.data[self.offset : self.offset + length]) self.offset += length return info def read_header(self): """Reads header portion of packet""" - (self.id, self.flags, self.num_questions, self.num_answers, - self.num_authorities, self.num_additionals) = self.unpack(b'!6H') + ( + self.id, + self.flags, + self.num_questions, + self.num_answers, + self.num_authorities, + self.num_additionals, + ) = self.unpack(b"!6H") def read_questions(self): """Reads questions section of packet""" for _ in range(self.num_questions): name = self.read_name() - type_, class_ = self.unpack(b'!HH') + type_, class_ = self.unpack(b"!HH") question = DNSQuestion(name, type_, class_) self.questions.append(question) @@ -234,13 +242,13 @@ def read_character_string(self): def read_string(self, length): """Reads a string of a given length from the packet""" - info = self.data[self.offset:self.offset + length] + info = self.data[self.offset : self.offset + length] self.offset += length return info def read_unsigned_short(self): """Reads an unsigned short from the packet""" - return self.unpack(b'!H')[0] + return self.unpack(b"!H")[0] def read_others(self): """Reads the answers, authorities and additionals section of the @@ -248,18 +256,15 @@ def read_others(self): n = self.num_answers + self.num_authorities + self.num_additionals for _ in range(n): domain = self.read_name() - type_, class_, ttl, length = self.unpack(b'!HHiH') + type_, class_, ttl, length = self.unpack(b"!HHiH") rec = None if type_ == _TYPE_A: - rec = DNSAddress( - domain, type_, class_, ttl, self.read_string(4)) + rec = DNSAddress(domain, type_, class_, ttl, self.read_string(4)) elif type_ == _TYPE_TXT: - rec = DNSText( - domain, type_, class_, ttl, self.read_string(length)) + rec = DNSText(domain, type_, class_, ttl, self.read_string(length)) elif type_ == _TYPE_AAAA: - rec = DNSAddress( - domain, type_, class_, ttl, self.read_string(16)) + rec = DNSAddress(domain, type_, class_, ttl, self.read_string(16)) else: # Try to ignore types we don't know about # Skip the payload for the resource record so the next @@ -279,11 +284,11 @@ def is_response(self): def read_utf(self, offset, length): """Reads a UTF-8 string of a given length from the packet""" - return str(self.data[offset:offset + length], 'utf-8', 'replace') + return str(self.data[offset : offset + length], "utf-8", "replace") def read_name(self): """Reads a domain name from the packet""" - result = '' + result = "" off = self.offset next_ = -1 first = off @@ -295,15 +300,14 @@ def read_name(self): break t = length & 0xC0 if t == 0x00: - result = ''.join((result, self.read_utf(off, length) + '.')) + result = "".join((result, self.read_utf(off, length) + ".")) off += length elif t == 0xC0: if next_ < 0: next_ = off + 1 off = ((length & 0x3F) << 8) | self.data[off] if off >= first: - raise IncomingDecodeError( - f"Bad domain name (circular) at {off}") + raise IncomingDecodeError(f"Bad domain name (circular) at {off}") first = off else: raise IncomingDecodeError(f"Bad domain name at {off}") @@ -341,20 +345,20 @@ def pack(self, format_, value): def write_byte(self, value): """Writes a single byte to the packet""" - self.pack(b'!c', int2byte(value)) + self.pack(b"!c", int2byte(value)) def insert_short(self, index, value): """Inserts an unsigned short in a certain position in the packet""" - self.data.insert(index, struct.pack(b'!H', value)) + self.data.insert(index, struct.pack(b"!H", value)) self.size += 2 def write_short(self, value): """Writes an unsigned short to the packet""" - self.pack(b'!H', value) + self.pack(b"!H", value) def write_int(self, value): """Writes an unsigned integer to the packet""" - self.pack(b'!I', int(value)) + self.pack(b"!I", int(value)) def write_string(self, value): """Writes a string to the packet""" @@ -364,7 +368,7 @@ def write_string(self, value): def write_utf(self, s): """Writes a UTF-8 string of a given length to the packet""" - utfstr = s.encode('utf-8') + utfstr = s.encode("utf-8") length = len(utfstr) self.write_byte(length) self.write_string(utfstr) @@ -377,12 +381,12 @@ def write_character_string(self, value): def write_name(self, name): # split name into each label - parts = name.split('.') + parts = name.split(".") if not parts[-1]: parts.pop() # construct each suffix - name_suffices = ['.'.join(parts[i:]) for i in range(len(parts))] + name_suffices = [".".join(parts[i:]) for i in range(len(parts))] # look for an existing name or suffix for count, sub_name in enumerate(name_suffices): @@ -392,9 +396,11 @@ def write_name(self, name): count = len(name_suffices) # note the new names we are saving into the packet - name_length = len(name.encode('utf-8')) + name_length = len(name.encode("utf-8")) for suffix in name_suffices[:count]: - self.names[suffix] = self.size + name_length - len(suffix.encode('utf-8')) - 1 + self.names[suffix] = ( + self.size + name_length - len(suffix.encode("utf-8")) - 1 + ) # write the new names out. for part in parts[:count]: @@ -427,12 +433,12 @@ def packet(self): self.insert_short(0, len(self.questions)) self.insert_short(0, self.flags) # _FLAGS_QR_QUERY self.insert_short(0, 0) - return b''.join(self.data) + return b"".join(self.data) class Engine(threading.Thread): def __init__(self, zc): - threading.Thread.__init__(self, name='zeroconf-Engine') + threading.Thread.__init__(self, name="zeroconf-Engine") self.daemon = True self.zc = zc self.readers = {} @@ -488,7 +494,7 @@ def handle_read(self, socket_): self.log_exception_warning() return - log.debug('Received from %r:%r: %r ', addr, port, data) + log.debug("Received from %r:%r: %r ", addr, port, data) self.data = data msg = DNSIncoming(data) @@ -530,8 +536,7 @@ def request(self, zc, timeout): return False if next_ <= now: out = DNSOutgoing(_FLAGS_QR_QUERY) - out.add_question( - DNSQuestion(self.name, _TYPE_A, _CLASS_IN)) + out.add_question(DNSQuestion(self.name, _TYPE_A, _CLASS_IN)) zc.send(out) next_ = now + delay delay *= 2 @@ -592,10 +597,12 @@ def run(self): while not self.stop_event.is_set(): self.purge_cache() for host in self.query_hosts: - if all(record.is_expired(time.time()) for record in self.cache.get(host, [])): + if all( + record.is_expired(time.time()) + for record in self.cache.get(host, []) + ): out = DNSOutgoing(_FLAGS_QR_QUERY) - out.add_question( - DNSQuestion(host, _TYPE_A, _CLASS_IN)) + out.add_question(DNSQuestion(host, _TYPE_A, _CLASS_IN)) self.zc.send(out) self.query_event.wait() self.query_event.clear() @@ -603,12 +610,15 @@ def run(self): def get_all_addresses(): - return list({ - addr.ip - for iface in ifaddr.get_adapters() - for addr in iface.ips - if addr.is_IPv4 and addr.network_prefix != 32 # Host only netmask 255.255.255.255 - }) + return list( + { + addr.ip + for iface in ifaddr.get_adapters() + for addr in iface.ips + if addr.is_IPv4 + and addr.network_prefix != 32 # Host only netmask 255.255.255.255 + } + ) def new_socket(): @@ -636,12 +646,12 @@ def new_socket(): # OpenBSD needs the ttl and loop values for the IP_MULTICAST_TTL and # IP_MULTICAST_LOOP socket options as an unsigned char. - ttl = struct.pack(b'B', 255) + ttl = struct.pack(b"B", 255) s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl) - loop = struct.pack(b'B', 1) + loop = struct.pack(b"B", 1) s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, loop) - s.bind(('', _MDNS_PORT)) + s.bind(("", _MDNS_PORT)) return s @@ -659,24 +669,28 @@ def __init__(self): try: _value = socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(i) self._listen_socket.setsockopt( - socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, _value) + socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, _value + ) except OSError as e: _errno = e.args[0] if _errno == errno.EADDRINUSE: log.info( - 'Address in use when adding %s to multicast group, ' - 'it is expected to happen on some systems', i, + "Address in use when adding %s to multicast group, " + "it is expected to happen on some systems", + i, ) elif _errno == errno.EADDRNOTAVAIL: log.info( - 'Address not available when adding %s to multicast ' - 'group, it is expected to happen on some systems', i, + "Address not available when adding %s to multicast " + "group, it is expected to happen on some systems", + i, ) continue elif _errno == errno.EINVAL: log.info( - 'Interface of %s does not support multicast, ' - 'it is expected in WSL', i + "Interface of %s does not support multicast, " + "it is expected in WSL", + i, ) continue @@ -685,7 +699,8 @@ def __init__(self): respond_socket = new_socket() respond_socket.setsockopt( - socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(i)) + socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(i) + ) self._respond_sockets.append(respond_socket) @@ -728,7 +743,7 @@ def remove_listener(self, listener): self.listeners.remove(listener) self.notify_all() except Exception as e: # pylint: disable=broad-except - log.exception('Unknown error, possibly benign: %r', e) + log.exception("Unknown error, possibly benign: %r", e) def update_record(self, now, rec): """Used to notify listeners of new information that has updated @@ -747,7 +762,7 @@ def handle_response(self, msg): def send(self, out): """Sends an outgoing packet.""" packet = out.packet() - log.debug('Sending %r (%d bytes) as %r...', out, len(packet), packet) + log.debug("Sending %r (%d bytes) as %r...", out, len(packet), packet) for s in self._respond_sockets: if self._GLOBAL_DONE: return @@ -759,8 +774,9 @@ def send(self, out): else: if bytes_sent != len(packet): self.log_warning_once( - '!!! sent %d out of %d bytes to %r' % ( - bytes_sent, len(packet), s)) + "!!! sent %d out of %d bytes to %r" + % (bytes_sent, len(packet), s) + ) def close(self): """Ends the background threads, and prevent this instance from diff --git a/platformio.ini b/platformio.ini index 557803bb2995..1dbc88cfdec8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,7 +14,7 @@ lib_deps = AsyncMqttClient-esphome@0.8.4 ArduinoJson-esphomelib@5.13.3 ESPAsyncWebServer-esphome@1.2.7 - FastLED@3.3.2 + fastled/FastLED@3.3.2 NeoPixelBus-esphome@2.5.7 ESPAsyncTCP-esphome@1.2.3 1655@1.0.2 ; TinyGPSPlus (has name conflict) diff --git a/pylintrc b/pylintrc index 00ffdc9f9a1a..8f2e9a7359c5 100644 --- a/pylintrc +++ b/pylintrc @@ -3,6 +3,7 @@ reports=no ignore=api_pb2.py disable= + format, missing-docstring, fixme, unused-argument, diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000000..7a75060c8e9c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.black] +target-version = ["py36", "py37", "py38"] +exclude = 'generated' diff --git a/requirements.txt b/requirements.txt index 78aa0442b990..9ab99e0bb4d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,14 @@ -voluptuous==0.12.0 -PyYAML==5.3.1 +voluptuous==0.12.1 +PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 -colorlog==4.6.2 +colorlog==4.7.2 tornado==6.1 -protobuf==3.13.0 +protobuf==3.15.6 tzlocal==2.1 -pytz==2020.5 +pytz==2021.1 pyserial==3.5 ifaddr==0.1.7 -platformio==5.0.4 +platformio==5.1.1 esptool==2.8 click==7.1.2 diff --git a/requirements_test.txt b/requirements_test.txt index 864dfe1a729b..f811626cc5f2 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,12 +1,14 @@ -pylint==2.6.0 -flake8==3.8.4 +pylint==2.7.2 +flake8==3.9.0 +black==20.8b1 pillow>4.0.0 cryptography>=2.0.0,<4 pexpect==4.8.0 +pre-commit # Unit tests -pytest==6.2.1 -pytest-cov==2.10.1 +pytest==6.2.2 +pytest-cov==2.11.1 pytest-mock==3.5.1 asyncmock==0.4.2 hypothesis==5.21.0 diff --git a/script/api_protobuf/api_options_pb2.py b/script/api_protobuf/api_options_pb2.py index e690a2c5d742..f5297c062cca 100644 --- a/script/api_protobuf/api_options_pb2.py +++ b/script/api_protobuf/api_options_pb2.py @@ -2,12 +2,14 @@ # source: api_options.proto import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) + +_b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1")) from google.protobuf.internal import enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database + # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -17,37 +19,38 @@ DESCRIPTOR = _descriptor.FileDescriptor( - name='api_options.proto', - package='', - syntax='proto2', - serialized_options=None, - serialized_pb=_b('\n\x11\x61pi_options.proto\x1a google/protobuf/descriptor.proto\"\x06\n\x04void*F\n\rAPISourceType\x12\x0f\n\x0bSOURCE_BOTH\x10\x00\x12\x11\n\rSOURCE_SERVER\x10\x01\x12\x11\n\rSOURCE_CLIENT\x10\x02:E\n\x16needs_setup_connection\x12\x1e.google.protobuf.MethodOptions\x18\x8e\x08 \x01(\x08:\x04true:C\n\x14needs_authentication\x12\x1e.google.protobuf.MethodOptions\x18\x8f\x08 \x01(\x08:\x04true:/\n\x02id\x12\x1f.google.protobuf.MessageOptions\x18\x8c\x08 \x01(\r:\x01\x30:M\n\x06source\x12\x1f.google.protobuf.MessageOptions\x18\x8d\x08 \x01(\x0e\x32\x0e.APISourceType:\x0bSOURCE_BOTH:/\n\x05ifdef\x12\x1f.google.protobuf.MessageOptions\x18\x8e\x08 \x01(\t:3\n\x03log\x12\x1f.google.protobuf.MessageOptions\x18\x8f\x08 \x01(\x08:\x04true:9\n\x08no_delay\x12\x1f.google.protobuf.MessageOptions\x18\x90\x08 \x01(\x08:\x05\x66\x61lse') - , - dependencies=[google_dot_protobuf_dot_descriptor__pb2.DESCRIPTOR,]) + name="api_options.proto", + package="", + syntax="proto2", + serialized_options=None, + serialized_pb=_b( + '\n\x11\x61pi_options.proto\x1a google/protobuf/descriptor.proto"\x06\n\x04void*F\n\rAPISourceType\x12\x0f\n\x0bSOURCE_BOTH\x10\x00\x12\x11\n\rSOURCE_SERVER\x10\x01\x12\x11\n\rSOURCE_CLIENT\x10\x02:E\n\x16needs_setup_connection\x12\x1e.google.protobuf.MethodOptions\x18\x8e\x08 \x01(\x08:\x04true:C\n\x14needs_authentication\x12\x1e.google.protobuf.MethodOptions\x18\x8f\x08 \x01(\x08:\x04true:/\n\x02id\x12\x1f.google.protobuf.MessageOptions\x18\x8c\x08 \x01(\r:\x01\x30:M\n\x06source\x12\x1f.google.protobuf.MessageOptions\x18\x8d\x08 \x01(\x0e\x32\x0e.APISourceType:\x0bSOURCE_BOTH:/\n\x05ifdef\x12\x1f.google.protobuf.MessageOptions\x18\x8e\x08 \x01(\t:3\n\x03log\x12\x1f.google.protobuf.MessageOptions\x18\x8f\x08 \x01(\x08:\x04true:9\n\x08no_delay\x12\x1f.google.protobuf.MessageOptions\x18\x90\x08 \x01(\x08:\x05\x66\x61lse' + ), + dependencies=[ + google_dot_protobuf_dot_descriptor__pb2.DESCRIPTOR, + ], +) _APISOURCETYPE = _descriptor.EnumDescriptor( - name='APISourceType', - full_name='APISourceType', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='SOURCE_BOTH', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='SOURCE_SERVER', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='SOURCE_CLIENT', index=2, number=2, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=63, - serialized_end=133, + name="APISourceType", + full_name="APISourceType", + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name="SOURCE_BOTH", index=0, number=0, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="SOURCE_SERVER", index=1, number=1, serialized_options=None, type=None + ), + _descriptor.EnumValueDescriptor( + name="SOURCE_CLIENT", index=2, number=2, serialized_options=None, type=None + ), + ], + containing_type=None, + serialized_options=None, + serialized_start=63, + serialized_end=133, ) _sym_db.RegisterEnumDescriptor(_APISOURCETYPE) @@ -58,105 +61,186 @@ NEEDS_SETUP_CONNECTION_FIELD_NUMBER = 1038 needs_setup_connection = _descriptor.FieldDescriptor( - name='needs_setup_connection', full_name='needs_setup_connection', index=0, - number=1038, type=8, cpp_type=7, label=1, - has_default_value=True, default_value=True, - message_type=None, enum_type=None, containing_type=None, - is_extension=True, extension_scope=None, - serialized_options=None, file=DESCRIPTOR) + name="needs_setup_connection", + full_name="needs_setup_connection", + index=0, + number=1038, + type=8, + cpp_type=7, + label=1, + has_default_value=True, + default_value=True, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=True, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, +) NEEDS_AUTHENTICATION_FIELD_NUMBER = 1039 needs_authentication = _descriptor.FieldDescriptor( - name='needs_authentication', full_name='needs_authentication', index=1, - number=1039, type=8, cpp_type=7, label=1, - has_default_value=True, default_value=True, - message_type=None, enum_type=None, containing_type=None, - is_extension=True, extension_scope=None, - serialized_options=None, file=DESCRIPTOR) + name="needs_authentication", + full_name="needs_authentication", + index=1, + number=1039, + type=8, + cpp_type=7, + label=1, + has_default_value=True, + default_value=True, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=True, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, +) ID_FIELD_NUMBER = 1036 id = _descriptor.FieldDescriptor( - name='id', full_name='id', index=2, - number=1036, type=13, cpp_type=3, label=1, - has_default_value=True, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=True, extension_scope=None, - serialized_options=None, file=DESCRIPTOR) + name="id", + full_name="id", + index=2, + number=1036, + type=13, + cpp_type=3, + label=1, + has_default_value=True, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=True, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, +) SOURCE_FIELD_NUMBER = 1037 source = _descriptor.FieldDescriptor( - name='source', full_name='source', index=3, - number=1037, type=14, cpp_type=8, label=1, - has_default_value=True, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=True, extension_scope=None, - serialized_options=None, file=DESCRIPTOR) + name="source", + full_name="source", + index=3, + number=1037, + type=14, + cpp_type=8, + label=1, + has_default_value=True, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=True, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, +) IFDEF_FIELD_NUMBER = 1038 ifdef = _descriptor.FieldDescriptor( - name='ifdef', full_name='ifdef', index=4, - number=1038, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=True, extension_scope=None, - serialized_options=None, file=DESCRIPTOR) + name="ifdef", + full_name="ifdef", + index=4, + number=1038, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=True, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, +) LOG_FIELD_NUMBER = 1039 log = _descriptor.FieldDescriptor( - name='log', full_name='log', index=5, - number=1039, type=8, cpp_type=7, label=1, - has_default_value=True, default_value=True, - message_type=None, enum_type=None, containing_type=None, - is_extension=True, extension_scope=None, - serialized_options=None, file=DESCRIPTOR) + name="log", + full_name="log", + index=5, + number=1039, + type=8, + cpp_type=7, + label=1, + has_default_value=True, + default_value=True, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=True, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, +) NO_DELAY_FIELD_NUMBER = 1040 no_delay = _descriptor.FieldDescriptor( - name='no_delay', full_name='no_delay', index=6, - number=1040, type=8, cpp_type=7, label=1, - has_default_value=True, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=True, extension_scope=None, - serialized_options=None, file=DESCRIPTOR) + name="no_delay", + full_name="no_delay", + index=6, + number=1040, + type=8, + cpp_type=7, + label=1, + has_default_value=True, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=True, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, +) _VOID = _descriptor.Descriptor( - name='void', - full_name='void', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=55, - serialized_end=61, + name="void", + full_name="void", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto2", + extension_ranges=[], + oneofs=[], + serialized_start=55, + serialized_end=61, ) -DESCRIPTOR.message_types_by_name['void'] = _VOID -DESCRIPTOR.enum_types_by_name['APISourceType'] = _APISOURCETYPE -DESCRIPTOR.extensions_by_name['needs_setup_connection'] = needs_setup_connection -DESCRIPTOR.extensions_by_name['needs_authentication'] = needs_authentication -DESCRIPTOR.extensions_by_name['id'] = id -DESCRIPTOR.extensions_by_name['source'] = source -DESCRIPTOR.extensions_by_name['ifdef'] = ifdef -DESCRIPTOR.extensions_by_name['log'] = log -DESCRIPTOR.extensions_by_name['no_delay'] = no_delay +DESCRIPTOR.message_types_by_name["void"] = _VOID +DESCRIPTOR.enum_types_by_name["APISourceType"] = _APISOURCETYPE +DESCRIPTOR.extensions_by_name["needs_setup_connection"] = needs_setup_connection +DESCRIPTOR.extensions_by_name["needs_authentication"] = needs_authentication +DESCRIPTOR.extensions_by_name["id"] = id +DESCRIPTOR.extensions_by_name["source"] = source +DESCRIPTOR.extensions_by_name["ifdef"] = ifdef +DESCRIPTOR.extensions_by_name["log"] = log +DESCRIPTOR.extensions_by_name["no_delay"] = no_delay _sym_db.RegisterFileDescriptor(DESCRIPTOR) -void = _reflection.GeneratedProtocolMessageType('void', (_message.Message,), dict( - DESCRIPTOR = _VOID, - __module__ = 'api_options_pb2' - # @@protoc_insertion_point(class_scope:void) - )) +void = _reflection.GeneratedProtocolMessageType( + "void", + (_message.Message,), + dict( + DESCRIPTOR=_VOID, + __module__="api_options_pb2" + # @@protoc_insertion_point(class_scope:void) + ), +) _sym_db.RegisterMessage(void) -google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(needs_setup_connection) -google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(needs_authentication) +google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension( + needs_setup_connection +) +google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension( + needs_authentication +) google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(id) source.enum_type = _APISOURCETYPE google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(source) diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index e78eff76c77a..f86145df2fed 100644 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -27,39 +27,39 @@ import api_options_pb2 as pb import google.protobuf.descriptor_pb2 as descriptor -file_header = '// This file was automatically generated with a tool.\n' -file_header += '// See scripts/api_protobuf/api_protobuf.py\n' +file_header = "// This file was automatically generated with a tool.\n" +file_header += "// See scripts/api_protobuf/api_protobuf.py\n" cwd = Path(__file__).resolve().parent -root = cwd.parent.parent / 'esphome' / 'components' / 'api' -prot = root / 'api.protoc' -call(['protoc', '-o', str(prot), '-I', str(root), 'api.proto']) +root = cwd.parent.parent / "esphome" / "components" / "api" +prot = root / "api.protoc" +call(["protoc", "-o", str(prot), "-I", str(root), "api.proto"]) content = prot.read_bytes() d = descriptor.FileDescriptorSet.FromString(content) -def indent_list(text, padding=' '): +def indent_list(text, padding=" "): return [padding + line for line in text.splitlines()] -def indent(text, padding=' '): - return '\n'.join(indent_list(text, padding)) +def indent(text, padding=" "): + return "\n".join(indent_list(text, padding)) def camel_to_snake(name): # https://stackoverflow.com/a/1176023 - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() + s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) + return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() -class TypeInfo(): +class TypeInfo: def __init__(self, field): self._field = field @property def default_value(self): - return '' + return "" @property def name(self): @@ -87,11 +87,11 @@ def cpp_type(self): @property def reference_type(self): - return f'{self.cpp_type} ' + return f"{self.cpp_type} " @property def const_reference_type(self): - return f'{self.cpp_type} ' + return f"{self.cpp_type} " @property def public_content(self) -> str: @@ -103,18 +103,20 @@ def protected_content(self) -> str: @property def class_member(self) -> str: - return f'{self.cpp_type} {self.field_name}{{{self.default_value}}}; // NOLINT' + return f"{self.cpp_type} {self.field_name}{{{self.default_value}}}; // NOLINT" @property def decode_varint_content(self) -> str: content = self.decode_varint if content is None: return None - return dedent(f'''\ + return dedent( + f"""\ case {self.number}: {{ this->{self.field_name} = {content}; return true; - }}''') + }}""" + ) decode_varint = None @@ -123,11 +125,13 @@ def decode_length_content(self) -> str: content = self.decode_length if content is None: return None - return dedent(f'''\ + return dedent( + f"""\ case {self.number}: {{ this->{self.field_name} = {content}; return true; - }}''') + }}""" + ) decode_length = None @@ -136,11 +140,13 @@ def decode_32bit_content(self) -> str: content = self.decode_32bit if content is None: return None - return dedent(f'''\ + return dedent( + f"""\ case {self.number}: {{ this->{self.field_name} = {content}; return true; - }}''') + }}""" + ) decode_32bit = None @@ -149,24 +155,26 @@ def decode_64bit_content(self) -> str: content = self.decode_64bit if content is None: return None - return dedent(f'''\ + return dedent( + f"""\ case {self.number}: {{ this->{self.field_name} = {content}; return true; - }}''') + }}""" + ) decode_64bit = None @property def encode_content(self): - return f'buffer.{self.encode_func}({self.number}, this->{self.field_name});' + return f"buffer.{self.encode_func}({self.number}, this->{self.field_name});" encode_func = None @property def dump_content(self): o = f'out.append(" {self.name}: ");\n' - o += self.dump(f'this->{self.field_name}') + '\n' + o += self.dump(f"this->{self.field_name}") + "\n" o += f'out.append("\\n");\n' return o @@ -186,115 +194,115 @@ def func(value): @register_type(1) class DoubleType(TypeInfo): - cpp_type = 'double' - default_value = '0.0' - decode_64bit = 'value.as_double()' - encode_func = 'encode_double' + cpp_type = "double" + default_value = "0.0" + decode_64bit = "value.as_double()" + encode_func = "encode_double" def dump(self, name): o = f'sprintf(buffer, "%g", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @register_type(2) class FloatType(TypeInfo): - cpp_type = 'float' - default_value = '0.0f' - decode_32bit = 'value.as_float()' - encode_func = 'encode_float' + cpp_type = "float" + default_value = "0.0f" + decode_32bit = "value.as_float()" + encode_func = "encode_float" def dump(self, name): o = f'sprintf(buffer, "%g", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @register_type(3) class Int64Type(TypeInfo): - cpp_type = 'int64_t' - default_value = '0' - decode_varint = 'value.as_int64()' - encode_func = 'encode_int64' + cpp_type = "int64_t" + default_value = "0" + decode_varint = "value.as_int64()" + encode_func = "encode_int64" def dump(self, name): o = f'sprintf(buffer, "%ll", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @register_type(4) class UInt64Type(TypeInfo): - cpp_type = 'uint64_t' - default_value = '0' - decode_varint = 'value.as_uint64()' - encode_func = 'encode_uint64' + cpp_type = "uint64_t" + default_value = "0" + decode_varint = "value.as_uint64()" + encode_func = "encode_uint64" def dump(self, name): o = f'sprintf(buffer, "%ull", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @register_type(5) class Int32Type(TypeInfo): - cpp_type = 'int32_t' - default_value = '0' - decode_varint = 'value.as_int32()' - encode_func = 'encode_int32' + cpp_type = "int32_t" + default_value = "0" + decode_varint = "value.as_int32()" + encode_func = "encode_int32" def dump(self, name): o = f'sprintf(buffer, "%d", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @register_type(6) class Fixed64Type(TypeInfo): - cpp_type = 'uint64_t' - default_value = '0' - decode_64bit = 'value.as_fixed64()' - encode_func = 'encode_fixed64' + cpp_type = "uint64_t" + default_value = "0" + decode_64bit = "value.as_fixed64()" + encode_func = "encode_fixed64" def dump(self, name): o = f'sprintf(buffer, "%ull", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @register_type(7) class Fixed32Type(TypeInfo): - cpp_type = 'uint32_t' - default_value = '0' - decode_32bit = 'value.as_fixed32()' - encode_func = 'encode_fixed32' + cpp_type = "uint32_t" + default_value = "0" + decode_32bit = "value.as_fixed32()" + encode_func = "encode_fixed32" def dump(self, name): o = f'sprintf(buffer, "%u", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @register_type(8) class BoolType(TypeInfo): - cpp_type = 'bool' - default_value = 'false' - decode_varint = 'value.as_bool()' - encode_func = 'encode_bool' + cpp_type = "bool" + default_value = "false" + decode_varint = "value.as_bool()" + encode_func = "encode_bool" def dump(self, name): - o = f'out.append(YESNO({name}));' + o = f"out.append(YESNO({name}));" return o @register_type(9) class StringType(TypeInfo): - cpp_type = 'std::string' - default_value = '' - reference_type = 'std::string &' - const_reference_type = 'const std::string &' - decode_length = 'value.as_string()' - encode_func = 'encode_string' + cpp_type = "std::string" + default_value = "" + reference_type = "std::string &" + const_reference_type = "const std::string &" + decode_length = "value.as_string()" + encode_func = "encode_string" def dump(self, name): o = f'out.append("\'").append({name}).append("\'");' @@ -307,37 +315,37 @@ class MessageType(TypeInfo): def cpp_type(self): return self._field.type_name[1:] - default_value = '' + default_value = "" @property def reference_type(self): - return f'{self.cpp_type} &' + return f"{self.cpp_type} &" @property def const_reference_type(self): - return f'const {self.cpp_type} &' + return f"const {self.cpp_type} &" @property def encode_func(self): - return f'encode_message<{self.cpp_type}>' + return f"encode_message<{self.cpp_type}>" @property def decode_length(self): - return f'value.as_message<{self.cpp_type}>()' + return f"value.as_message<{self.cpp_type}>()" def dump(self, name): - o = f'{name}.dump_to(out);' + o = f"{name}.dump_to(out);" return o @register_type(12) class BytesType(TypeInfo): - cpp_type = 'std::string' - default_value = '' - reference_type = 'std::string &' - const_reference_type = 'const std::string &' - decode_length = 'value.as_string()' - encode_func = 'encode_string' + cpp_type = "std::string" + default_value = "" + reference_type = "std::string &" + const_reference_type = "const std::string &" + decode_length = "value.as_string()" + encode_func = "encode_string" def dump(self, name): o = f'out.append("\'").append({name}).append("\'");' @@ -346,14 +354,14 @@ def dump(self, name): @register_type(13) class UInt32Type(TypeInfo): - cpp_type = 'uint32_t' - default_value = '0' - decode_varint = 'value.as_uint32()' - encode_func = 'encode_uint32' + cpp_type = "uint32_t" + default_value = "0" + decode_varint = "value.as_uint32()" + encode_func = "encode_uint32" def dump(self, name): o = f'sprintf(buffer, "%u", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @@ -361,72 +369,72 @@ def dump(self, name): class EnumType(TypeInfo): @property def cpp_type(self): - return f'enums::{self._field.type_name[1:]}' + return f"enums::{self._field.type_name[1:]}" @property def decode_varint(self): - return f'value.as_enum<{self.cpp_type}>()' + return f"value.as_enum<{self.cpp_type}>()" - default_value = '' + default_value = "" @property def encode_func(self): - return f'encode_enum<{self.cpp_type}>' + return f"encode_enum<{self.cpp_type}>" def dump(self, name): - o = f'out.append(proto_enum_to_string<{self.cpp_type}>({name}));' + o = f"out.append(proto_enum_to_string<{self.cpp_type}>({name}));" return o @register_type(15) class SFixed32Type(TypeInfo): - cpp_type = 'int32_t' - default_value = '0' - decode_32bit = 'value.as_sfixed32()' - encode_func = 'encode_sfixed32' + cpp_type = "int32_t" + default_value = "0" + decode_32bit = "value.as_sfixed32()" + encode_func = "encode_sfixed32" def dump(self, name): o = f'sprintf(buffer, "%d", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @register_type(16) class SFixed64Type(TypeInfo): - cpp_type = 'int64_t' - default_value = '0' - decode_64bit = 'value.as_sfixed64()' - encode_func = 'encode_sfixed64' + cpp_type = "int64_t" + default_value = "0" + decode_64bit = "value.as_sfixed64()" + encode_func = "encode_sfixed64" def dump(self, name): o = f'sprintf(buffer, "%ll", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @register_type(17) class SInt32Type(TypeInfo): - cpp_type = 'int32_t' - default_value = '0' - decode_varint = 'value.as_sint32()' - encode_func = 'encode_sint32' + cpp_type = "int32_t" + default_value = "0" + decode_varint = "value.as_sint32()" + encode_func = "encode_sint32" def dump(self, name): o = f'sprintf(buffer, "%d", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @register_type(18) class SInt64Type(TypeInfo): - cpp_type = 'int64_t' - default_value = '0' - decode_varint = 'value.as_sint64()' - encode_func = 'encode_sin64' + cpp_type = "int64_t" + default_value = "0" + decode_varint = "value.as_sint64()" + encode_func = "encode_sin64" def dump(self): o = f'sprintf(buffer, "%ll", {name});\n' - o += f'out.append(buffer);' + o += f"out.append(buffer);" return o @@ -437,59 +445,67 @@ def __init__(self, field): @property def cpp_type(self): - return f'std::vector<{self._ti.cpp_type}>' + return f"std::vector<{self._ti.cpp_type}>" @property def reference_type(self): - return f'{self.cpp_type} &' + return f"{self.cpp_type} &" @property def const_reference_type(self): - return f'const {self.cpp_type} &' + return f"const {self.cpp_type} &" @property def decode_varint_content(self) -> str: content = self._ti.decode_varint if content is None: return None - return dedent(f'''\ + return dedent( + f"""\ case {self.number}: {{ this->{self.field_name}.push_back({content}); return true; - }}''') + }}""" + ) @property def decode_length_content(self) -> str: content = self._ti.decode_length if content is None: return None - return dedent(f'''\ + return dedent( + f"""\ case {self.number}: {{ this->{self.field_name}.push_back({content}); return true; - }}''') + }}""" + ) @property def decode_32bit_content(self) -> str: content = self._ti.decode_32bit if content is None: return None - return dedent(f'''\ + return dedent( + f"""\ case {self.number}: {{ this->{self.field_name}.push_back({content}); return true; - }}''') + }}""" + ) @property def decode_64bit_content(self) -> str: content = self._ti.decode_64bit if content is None: return None - return dedent(f'''\ + return dedent( + f"""\ case {self.number}: {{ this->{self.field_name}.push_back({content}); return true; - }}''') + }}""" + ) @property def _ti_is_bool(self): @@ -507,9 +523,9 @@ def encode_content(self): def dump_content(self): o = f'for (const auto {"" if self._ti_is_bool else "&"}it : this->{self.field_name}) {{\n' o += f' out.append(" {self.name}: ");\n' - o += indent(self._ti.dump('it')) + '\n' + o += indent(self._ti.dump("it")) + "\n" o += f' out.append("\\n");\n' - o += f'}}\n' + o += f"}}\n" return o @@ -517,8 +533,8 @@ def build_enum_type(desc): name = desc.name out = f"enum {name} : uint32_t {{\n" for v in desc.value: - out += f' {v.name} = {v.number},\n' - out += '};\n' + out += f" {v.name} = {v.number},\n" + out += "};\n" cpp = f"template<>\n" cpp += f"const char *proto_enum_to_string(enums::{name} value) {{\n" @@ -526,8 +542,8 @@ def build_enum_type(desc): for v in desc.value: cpp += f' case enums::{v.name}: return "{v.name}";\n' cpp += f' default: return "UNKNOWN";\n' - cpp += f' }}\n' - cpp += f'}}\n' + cpp += f" }}\n" + cpp += f"}}\n" return out, cpp @@ -562,80 +578,80 @@ def build_message_type(desc): if ti.dump_content: dump.append(ti.dump_content) - cpp = '' + cpp = "" if decode_varint: - decode_varint.append('default:\n return false;') - o = f'bool {desc.name}::decode_varint(uint32_t field_id, ProtoVarInt value) {{\n' - o += ' switch (field_id) {\n' - o += indent("\n".join(decode_varint), ' ') + '\n' - o += ' }\n' - o += '}\n' + decode_varint.append("default:\n return false;") + o = f"bool {desc.name}::decode_varint(uint32_t field_id, ProtoVarInt value) {{\n" + o += " switch (field_id) {\n" + o += indent("\n".join(decode_varint), " ") + "\n" + o += " }\n" + o += "}\n" cpp += o - prot = 'bool decode_varint(uint32_t field_id, ProtoVarInt value) override;' + prot = "bool decode_varint(uint32_t field_id, ProtoVarInt value) override;" protected_content.insert(0, prot) if decode_length: - decode_length.append('default:\n return false;') - o = f'bool {desc.name}::decode_length(uint32_t field_id, ProtoLengthDelimited value) {{\n' - o += ' switch (field_id) {\n' - o += indent("\n".join(decode_length), ' ') + '\n' - o += ' }\n' - o += '}\n' + decode_length.append("default:\n return false;") + o = f"bool {desc.name}::decode_length(uint32_t field_id, ProtoLengthDelimited value) {{\n" + o += " switch (field_id) {\n" + o += indent("\n".join(decode_length), " ") + "\n" + o += " }\n" + o += "}\n" cpp += o - prot = 'bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;' + prot = "bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;" protected_content.insert(0, prot) if decode_32bit: - decode_32bit.append('default:\n return false;') - o = f'bool {desc.name}::decode_32bit(uint32_t field_id, Proto32Bit value) {{\n' - o += ' switch (field_id) {\n' - o += indent("\n".join(decode_32bit), ' ') + '\n' - o += ' }\n' - o += '}\n' + decode_32bit.append("default:\n return false;") + o = f"bool {desc.name}::decode_32bit(uint32_t field_id, Proto32Bit value) {{\n" + o += " switch (field_id) {\n" + o += indent("\n".join(decode_32bit), " ") + "\n" + o += " }\n" + o += "}\n" cpp += o - prot = 'bool decode_32bit(uint32_t field_id, Proto32Bit value) override;' + prot = "bool decode_32bit(uint32_t field_id, Proto32Bit value) override;" protected_content.insert(0, prot) if decode_64bit: - decode_64bit.append('default:\n return false;') - o = f'bool {desc.name}::decode_64bit(uint32_t field_id, Proto64bit value) {{\n' - o += ' switch (field_id) {\n' - o += indent("\n".join(decode_64bit), ' ') + '\n' - o += ' }\n' - o += '}\n' + decode_64bit.append("default:\n return false;") + o = f"bool {desc.name}::decode_64bit(uint32_t field_id, Proto64bit value) {{\n" + o += " switch (field_id) {\n" + o += indent("\n".join(decode_64bit), " ") + "\n" + o += " }\n" + o += "}\n" cpp += o - prot = 'bool decode_64bit(uint32_t field_id, Proto64bit value) override;' + prot = "bool decode_64bit(uint32_t field_id, Proto64bit value) override;" protected_content.insert(0, prot) o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{\n" - o += indent('\n'.join(encode)) + '\n' - o += '}\n' + o += indent("\n".join(encode)) + "\n" + o += "}\n" cpp += o - prot = 'void encode(ProtoWriteBuffer buffer) const override;' + prot = "void encode(ProtoWriteBuffer buffer) const override;" public_content.append(prot) o = f"void {desc.name}::dump_to(std::string &out) const {{\n" if dump: o += f" char buffer[64];\n" o += f' out.append("{desc.name} {{\\n");\n' - o += indent('\n'.join(dump)) + '\n' + o += indent("\n".join(dump)) + "\n" o += f' out.append("}}");\n' else: o += f' out.append("{desc.name} {{}}");\n' - o += '}\n' + o += "}\n" cpp += o - prot = 'void dump_to(std::string &out) const override;' + prot = "void dump_to(std::string &out) const override;" public_content.append(prot) out = f"class {desc.name} : public ProtoMessage {{\n" - out += ' public:\n' - out += indent('\n'.join(public_content)) + '\n' - out += ' protected:\n' - out += indent('\n'.join(protected_content)) + '\n' + out += " public:\n" + out += indent("\n".join(public_content)) + "\n" + out += " protected:\n" + out += indent("\n".join(protected_content)) + "\n" out += "};\n" return out, cpp file = d.file[0] content = file_header -content += '''\ +content += """\ #pragma once #include "proto.h" @@ -643,26 +659,26 @@ def build_message_type(desc): namespace esphome { namespace api { -''' +""" cpp = file_header -cpp += '''\ +cpp += """\ #include "api_pb2.h" #include "esphome/core/log.h" namespace esphome { namespace api { -''' +""" -content += 'namespace enums {\n\n' +content += "namespace enums {\n\n" for enum in file.enum_type: s, c = build_enum_type(enum) content += s cpp += c -content += '\n} // namespace enums\n\n' +content += "\n} // namespace enums\n\n" mt = file.message_type @@ -671,21 +687,21 @@ def build_message_type(desc): content += s cpp += c -content += '''\ +content += """\ } // namespace api } // namespace esphome -''' -cpp += '''\ +""" +cpp += """\ } // namespace api } // namespace esphome -''' +""" -with open(root / 'api_pb2.h', 'w') as f: +with open(root / "api_pb2.h", "w") as f: f.write(content) -with open(root / 'api_pb2.cpp', 'w') as f: +with open(root / "api_pb2.cpp", "w") as f: f.write(cpp) SOURCE_BOTH = 0 @@ -694,7 +710,7 @@ def build_message_type(desc): RECEIVE_CASES = {} -class_name = 'APIServerConnectionBase' +class_name = "APIServerConnectionBase" ifdefs = {} @@ -716,50 +732,50 @@ def build_service_message_type(mt): ifdef = get_opt(mt, pb.ifdef) log = get_opt(mt, pb.log, True) nodelay = get_opt(mt, pb.no_delay, False) - hout = '' - cout = '' + hout = "" + cout = "" if ifdef is not None: ifdefs[str(mt.name)] = ifdef - hout += f'#ifdef {ifdef}\n' - cout += f'#ifdef {ifdef}\n' + hout += f"#ifdef {ifdef}\n" + cout += f"#ifdef {ifdef}\n" if source in (SOURCE_BOTH, SOURCE_SERVER): # Generate send - func = f'send_{snake}' - hout += f'bool {func}(const {mt.name} &msg);\n' - cout += f'bool {class_name}::{func}(const {mt.name} &msg) {{\n' + func = f"send_{snake}" + hout += f"bool {func}(const {mt.name} &msg);\n" + cout += f"bool {class_name}::{func}(const {mt.name} &msg) {{\n" if log: cout += f' ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' # cout += f' this->set_nodelay({str(nodelay).lower()});\n' - cout += f' return this->send_message_<{mt.name}>(msg, {id_});\n' - cout += f'}}\n' + cout += f" return this->send_message_<{mt.name}>(msg, {id_});\n" + cout += f"}}\n" if source in (SOURCE_BOTH, SOURCE_CLIENT): # Generate receive - func = f'on_{snake}' - hout += f'virtual void {func}(const {mt.name} &value){{}};\n' - case = '' + func = f"on_{snake}" + hout += f"virtual void {func}(const {mt.name} &value){{}};\n" + case = "" if ifdef is not None: - case += f'#ifdef {ifdef}\n' - case += f'{mt.name} msg;\n' - case += f'msg.decode(msg_data, msg_size);\n' + case += f"#ifdef {ifdef}\n" + case += f"{mt.name} msg;\n" + case += f"msg.decode(msg_data, msg_size);\n" if log: case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' - case += f'this->{func}(msg);\n' + case += f"this->{func}(msg);\n" if ifdef is not None: - case += f'#endif\n' - case += 'break;' + case += f"#endif\n" + case += "break;" RECEIVE_CASES[id_] = case if ifdef is not None: - hout += f'#endif\n' - cout += f'#endif\n' + hout += f"#endif\n" + cout += f"#endif\n" return hout, cout hpp = file_header -hpp += '''\ +hpp += """\ #pragma once #include "api_pb2.h" @@ -768,10 +784,10 @@ def build_service_message_type(mt): namespace esphome { namespace api { -''' +""" cpp = file_header -cpp += '''\ +cpp += """\ #include "api_pb2_service.h" #include "esphome/core/log.h" @@ -780,113 +796,113 @@ def build_service_message_type(mt): static const char *TAG = "api.service"; -''' +""" -hpp += f'class {class_name} : public ProtoService {{\n' -hpp += ' public:\n' +hpp += f"class {class_name} : public ProtoService {{\n" +hpp += " public:\n" for mt in file.message_type: obj = build_service_message_type(mt) if obj is None: continue hout, cout = obj - hpp += indent(hout) + '\n' + hpp += indent(hout) + "\n" cpp += cout cases = list(RECEIVE_CASES.items()) cases.sort() -hpp += ' protected:\n' -hpp += f' bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n' -out = f'bool {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n' -out += f' switch(msg_type) {{\n' +hpp += " protected:\n" +hpp += f" bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" +out = f"bool {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" +out += f" switch(msg_type) {{\n" for i, case in cases: - c = f'case {i}: {{\n' - c += indent(case) + '\n' - c += f'}}' - out += indent(c, ' ') + '\n' -out += ' default: \n' -out += ' return false;\n' -out += ' }\n' -out += ' return true;\n' -out += '}\n' + c = f"case {i}: {{\n" + c += indent(case) + "\n" + c += f"}}" + out += indent(c, " ") + "\n" +out += " default: \n" +out += " return false;\n" +out += " }\n" +out += " return true;\n" +out += "}\n" cpp += out -hpp += '};\n' +hpp += "};\n" serv = file.service[0] -class_name = 'APIServerConnection' -hpp += '\n' -hpp += f'class {class_name} : public {class_name}Base {{\n' -hpp += ' public:\n' -hpp_protected = '' -cpp += '\n' +class_name = "APIServerConnection" +hpp += "\n" +hpp += f"class {class_name} : public {class_name}Base {{\n" +hpp += " public:\n" +hpp_protected = "" +cpp += "\n" m = serv.method[0] for m in serv.method: func = m.name inp = m.input_type[1:] ret = m.output_type[1:] - is_void = ret == 'void' + is_void = ret == "void" snake = camel_to_snake(inp) - on_func = f'on_{snake}' + on_func = f"on_{snake}" needs_conn = get_opt(m, pb.needs_setup_connection, True) needs_auth = get_opt(m, pb.needs_authentication, True) ifdef = ifdefs.get(inp, None) if ifdef is not None: - hpp += f'#ifdef {ifdef}\n' - hpp_protected += f'#ifdef {ifdef}\n' - cpp += f'#ifdef {ifdef}\n' - - hpp_protected += f' void {on_func}(const {inp} &msg) override;\n' - hpp += f' virtual {ret} {func}(const {inp} &msg) = 0;\n' - cpp += f'void {class_name}::{on_func}(const {inp} &msg) {{\n' - body = '' + hpp += f"#ifdef {ifdef}\n" + hpp_protected += f"#ifdef {ifdef}\n" + cpp += f"#ifdef {ifdef}\n" + + hpp_protected += f" void {on_func}(const {inp} &msg) override;\n" + hpp += f" virtual {ret} {func}(const {inp} &msg) = 0;\n" + cpp += f"void {class_name}::{on_func}(const {inp} &msg) {{\n" + body = "" if needs_conn: - body += 'if (!this->is_connection_setup()) {\n' - body += ' this->on_no_setup_connection();\n' - body += ' return;\n' - body += '}\n' + body += "if (!this->is_connection_setup()) {\n" + body += " this->on_no_setup_connection();\n" + body += " return;\n" + body += "}\n" if needs_auth: - body += 'if (!this->is_authenticated()) {\n' - body += ' this->on_unauthenticated_access();\n' - body += ' return;\n' - body += '}\n' + body += "if (!this->is_authenticated()) {\n" + body += " this->on_unauthenticated_access();\n" + body += " return;\n" + body += "}\n" if is_void: - body += f'this->{func}(msg);\n' + body += f"this->{func}(msg);\n" else: - body += f'{ret} ret = this->{func}(msg);\n' + body += f"{ret} ret = this->{func}(msg);\n" ret_snake = camel_to_snake(ret) - body += f'if (!this->send_{ret_snake}(ret)) {{\n' - body += f' this->on_fatal_error();\n' - body += '}\n' - cpp += indent(body) + '\n' + '}\n' + body += f"if (!this->send_{ret_snake}(ret)) {{\n" + body += f" this->on_fatal_error();\n" + body += "}\n" + cpp += indent(body) + "\n" + "}\n" if ifdef is not None: - hpp += f'#endif\n' - hpp_protected += f'#endif\n' - cpp += f'#endif\n' + hpp += f"#endif\n" + hpp_protected += f"#endif\n" + cpp += f"#endif\n" -hpp += ' protected:\n' +hpp += " protected:\n" hpp += hpp_protected -hpp += '};\n' +hpp += "};\n" -hpp += '''\ +hpp += """\ } // namespace api } // namespace esphome -''' -cpp += '''\ +""" +cpp += """\ } // namespace api } // namespace esphome -''' +""" -with open(root / 'api_pb2_service.h', 'w') as f: +with open(root / "api_pb2_service.h", "w") as f: f.write(hpp) -with open(root / 'api_pb2_service.cpp', 'w') as f: +with open(root / "api_pb2_service.cpp", "w") as f: f.write(cpp) prot.unlink() diff --git a/script/build_codeowners.py b/script/build_codeowners.py index f21e9ca2a57d..2ee7521b911b 100755 --- a/script/build_codeowners.py +++ b/script/build_codeowners.py @@ -9,13 +9,14 @@ from esphome.core import CORE parser = argparse.ArgumentParser() -parser.add_argument('--check', help="Check if the CODEOWNERS file is up to date.", - action='store_true') +parser.add_argument( + "--check", help="Check if the CODEOWNERS file is up to date.", action="store_true" +) args = parser.parse_args() # The root directory of the repo root = Path(__file__).parent.parent -components_dir = root / 'esphome' / 'components' +components_dir = root / "esphome" / "components" BASE = """ # This file is generated by script/build_codeowners.py @@ -35,7 +36,7 @@ parts = [BASE] -# Fake some diretory so that get_component works +# Fake some directory so that get_component works CORE.config_path = str(root) codeowners = defaultdict(list) @@ -43,16 +44,18 @@ for path in components_dir.iterdir(): if not path.is_dir(): continue - if not (path / '__init__.py').is_file(): + if not (path / "__init__.py").is_file(): continue name = path.name comp = get_component(name) if comp is None: - print(f'Cannot find component {name}. Make sure current path is pip installed ESPHome') + print( + f"Cannot find component {name}. Make sure current path is pip installed ESPHome" + ) sys.exit(1) - codeowners[f'esphome/components/{name}/*'].extend(comp.codeowners) + codeowners[f"esphome/components/{name}/*"].extend(comp.codeowners) for platform_path in path.iterdir(): platform_name = platform_path.stem @@ -62,15 +65,17 @@ if platform_path.is_dir(): # Sub foldered platforms get their own line - if not (platform_path / '__init__.py').is_file(): + if not (platform_path / "__init__.py").is_file(): continue - codeowners[f'esphome/components/{name}/{platform_name}/*'].extend(platform.codeowners) + codeowners[f"esphome/components/{name}/{platform_name}/*"].extend( + platform.codeowners + ) continue # Non-subfoldered platforms add to codeowners at component level - if not platform_path.is_file() or platform_path.name == '__init__.py': + if not platform_path.is_file() or platform_path.name == "__init__.py": continue - codeowners[f'esphome/components/{name}/*'].extend(platform.codeowners) + codeowners[f"esphome/components/{name}/*"].extend(platform.codeowners) for path, owners in sorted(codeowners.items()): @@ -78,16 +83,18 @@ if not owners: continue for owner in owners: - if not owner.startswith('@'): - print(f"Codeowner {owner} for integration {path} must start with an '@' symbol!") + if not owner.startswith("@"): + print( + f"Codeowner {owner} for integration {path} must start with an '@' symbol!" + ) sys.exit(1) parts.append(f"{path} {' '.join(owners)}") # End newline -parts.append('') -content = '\n'.join(parts) -codeowners_file = root / 'CODEOWNERS' +parts.append("") +content = "\n".join(parts) +codeowners_file = root / "CODEOWNERS" if args.check: if codeowners_file.read_text() != content: diff --git a/script/build_compile_commands.py b/script/build_compile_commands.py index f0fc48ad98cd..4ac14f08b4d9 100755 --- a/script/build_compile_commands.py +++ b/script/build_compile_commands.py @@ -12,5 +12,5 @@ def main(): print("Done.") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/script/build_jsonschema.py b/script/build_jsonschema.py new file mode 100644 index 000000000000..2c9534b861d6 --- /dev/null +++ b/script/build_jsonschema.py @@ -0,0 +1,742 @@ +#!/usr/bin/env python3 + +import json +import argparse +import os +import re +from pathlib import Path +import voluptuous as vol + +# NOTE: Cannot import other esphome components globally as a modification in jsonschema +# is needed before modules are loaded +import esphome.jsonschema as ejs + +ejs.EnableJsonSchemaCollect = True + +DUMP_COMMENTS = False + +JSC_ACTION = "automation.ACTION_REGISTRY" +JSC_ALLOF = "allOf" +JSC_ANYOF = "anyOf" +JSC_COMMENT = "$comment" +JSC_CONDITION = "automation.CONDITION_REGISTRY" +JSC_DESCRIPTION = "description" +JSC_ONEOF = "oneOf" +JSC_PROPERTIES = "properties" +JSC_REF = "$ref" +SIMPLE_AUTOMATION = "simple_automation" + +schema_names = {} +schema_registry = {} +components = {} +modules = {} +registries = [] +pending_refs = [] + +definitions = {} +base_props = {} + + +parser = argparse.ArgumentParser() +parser.add_argument( + "--output", default="esphome.json", help="Output filename", type=os.path.abspath +) + +args = parser.parse_args() + + +def get_ref(definition): + return {JSC_REF: "#/definitions/" + definition} + + +def is_ref(jschema): + return isinstance(jschema, dict) and JSC_REF in jschema + + +def unref(jschema): + return definitions[jschema[JSC_REF][len("#/definitions/") :]] + + +def add_definition_array_or_single_object(ref): + return {JSC_ANYOF: [{"type": "array", "items": ref}, ref]} + + +def add_core(): + from esphome.core_config import CONFIG_SCHEMA + + base_props["esphome"] = get_jschema("esphome", CONFIG_SCHEMA.schema) + + +def add_buses(): + # uart + from esphome.components.uart import UART_DEVICE_SCHEMA + + get_jschema("uart_bus", UART_DEVICE_SCHEMA) + + # spi + from esphome.components.spi import spi_device_schema + + get_jschema("spi_bus", spi_device_schema(False)) + + # i2c + from esphome.components.i2c import i2c_device_schema + + get_jschema("i2c_bus", i2c_device_schema(None)) + + +def add_registries(): + for domain, module in modules.items(): + add_module_registries(domain, module) + + +def add_module_registries(domain, module): + from esphome.util import Registry + + for c in dir(module): + m = getattr(module, c) + if isinstance(m, Registry): + add_registry(domain + "." + c, m) + + +def add_registry(registry_name, registry): + validators = [] + registries.append((registry, registry_name)) + for name in registry.keys(): + schema = get_jschema(str(name), registry[name].schema, create_return_ref=False) + if not schema: + schema = {"type": "string"} + o_schema = {"type": "object", JSC_PROPERTIES: {name: schema}} + validators.append(o_schema) + definitions[registry_name] = {JSC_ANYOF: validators} + + +def get_registry_ref(registry): + # we don't know yet + ref = {JSC_REF: "pending"} + pending_refs.append((ref, registry)) + return ref + + +def solve_pending_refs(): + for ref, registry in pending_refs: + for registry_match, name in registries: + if registry == registry_match: + ref[JSC_REF] = "#/definitions/" + name + + +def add_module_schemas(name, module): + import esphome.config_validation as cv + + for c in dir(module): + v = getattr(module, c) + if isinstance(v, cv.Schema): + get_jschema(name + "." + c, v) + + +def get_dirs(): + from esphome.config import CORE_COMPONENTS_PATH + + dir_names = [ + d + for d in os.listdir(CORE_COMPONENTS_PATH) + if not d.startswith("__") + and os.path.isdir(os.path.join(CORE_COMPONENTS_PATH, d)) + ] + return dir_names + + +def get_logger_tags(): + from esphome.config import CORE_COMPONENTS_PATH + import glob + + pattern = re.compile(r'^static const char(\*\s|\s\*)TAG = "(\w.*)";', re.MULTILINE) + tags = [ + "app", + "component", + "esphal", + "helpers", + "preferences", + "scheduler", + "api.service", + ] + for x in os.walk(CORE_COMPONENTS_PATH): + for y in glob.glob(os.path.join(x[0], "*.cpp")): + with open(y, "r") as file: + data = file.read() + match = pattern.search(data) + if match: + tags.append(match.group(2)) + return tags + + +def load_components(): + import esphome.config_validation as cv + from esphome.config import get_component + + modules["cv"] = cv + from esphome import automation + + modules["automation"] = automation + + for domain in get_dirs(): + components[domain] = get_component(domain) + modules[domain] = components[domain].module + + +def add_components(): + from esphome.config import get_platform + + for domain, c in components.items(): + if c.is_platform_component: + # this is a platform_component, e.g. binary_sensor + platform_schema = [ + { + "type": "object", + "properties": {"platform": {"type": "string"}}, + } + ] + if domain not in ("output", "display"): + # output bases are either FLOAT or BINARY so don't add common base for this + # display bases are either simple or FULL so don't add common base for this + platform_schema = [ + {"$ref": f"#/definitions/{domain}.{domain.upper()}_SCHEMA"} + ] + platform_schema + + base_props[domain] = {"type": "array", "items": {"allOf": platform_schema}} + + add_module_registries(domain, c.module) + add_module_schemas(domain, c.module) + + # need first to iterate all platforms then iteate components + # a platform component can have other components as properties, + # e.g. climate components usually have a temperature sensor + + for domain, c in components.items(): + if (c.config_schema is not None) or c.is_platform_component: + if c.is_platform_component: + platform_schema = base_props[domain]["items"]["allOf"] + for platform in get_dirs(): + p = get_platform(domain, platform) + if p is not None: + # this is a platform element, e.g. + # - platform: gpio + schema = get_jschema( + domain + "-" + platform, + p.config_schema, + create_return_ref=False, + ) + if ( + schema + ): # for invalid schemas, None is returned thus is deprecated + platform_schema.append( + { + "if": { + JSC_PROPERTIES: { + "platform": {"const": platform} + } + }, + "then": schema, + } + ) + + elif c.config_schema is not None: + # adds root components which are not platforms, e.g. api: logger: + if c.is_multi_conf: + schema = get_jschema(domain, c.config_schema) + schema = add_definition_array_or_single_object(schema) + else: + schema = get_jschema(domain, c.config_schema, False) + base_props[domain] = schema + + +def get_automation_schema(name, vschema): + from esphome.automation import AUTOMATION_SCHEMA + + # ensure SIMPLE_AUTOMATION + if SIMPLE_AUTOMATION not in definitions: + simple_automation = add_definition_array_or_single_object(get_ref(JSC_ACTION)) + simple_automation[JSC_ANYOF].append( + get_jschema(AUTOMATION_SCHEMA.__module__, AUTOMATION_SCHEMA) + ) + + definitions[schema_names[str(AUTOMATION_SCHEMA)]][JSC_PROPERTIES][ + "then" + ] = add_definition_array_or_single_object(get_ref(JSC_ACTION)) + definitions[SIMPLE_AUTOMATION] = simple_automation + + extra_vschema = None + if AUTOMATION_SCHEMA == ejs.extended_schemas[str(vschema)][0]: + extra_vschema = ejs.extended_schemas[str(vschema)][1] + + if not extra_vschema: + return get_ref(SIMPLE_AUTOMATION) + + # add then property + extra_jschema = get_jschema(name, extra_vschema, False) + + if is_ref(extra_jschema): + return extra_jschema + + if not JSC_PROPERTIES in extra_jschema: + # these are interval: and exposure_notifications, featuring automations a component + extra_jschema[JSC_ALLOF][0][JSC_PROPERTIES][ + "then" + ] = add_definition_array_or_single_object(get_ref(JSC_ACTION)) + ref = create_ref(name, extra_vschema, extra_jschema) + return add_definition_array_or_single_object(ref) + + # automations can be either + # * a single action, + # * an array of action, + # * an object with automation's schema and a then key + # with again a single action or an array of actions + + extra_jschema[JSC_PROPERTIES]["then"] = add_definition_array_or_single_object( + get_ref(JSC_ACTION) + ) + jschema = add_definition_array_or_single_object(get_ref(JSC_ACTION)) + jschema[JSC_ANYOF].append(extra_jschema) + + return create_ref(name, extra_vschema, jschema) + + +def get_entry(parent_key, vschema): + from esphome.voluptuous_schema import _Schema as schema_type + + entry = {} + # annotate schema validator info + if DUMP_COMMENTS: + entry[JSC_COMMENT] = "entry: " + parent_key + "/" + str(vschema) + + if isinstance(vschema, list): + ref = get_jschema(parent_key + "[]", vschema[0]) + entry = {"type": "array", "items": ref} + elif isinstance(vschema, schema_type) and hasattr(vschema, "schema"): + entry = get_jschema(parent_key, vschema, False) + elif hasattr(vschema, "validators"): + entry = get_jschema(parent_key, vschema, False) + elif vschema in schema_registry: + entry = schema_registry[vschema].copy() + elif str(vschema) in ejs.registry_schemas: + entry = get_registry_ref(ejs.registry_schemas[str(vschema)]) + elif str(vschema) in ejs.list_schemas: + ref = get_jschema(parent_key, ejs.list_schemas[str(vschema)][0]) + entry = {JSC_ANYOF: [ref, {"type": "array", "items": ref}]} + + elif str(vschema) in ejs.typed_schemas: + schema_types = [{"type": "object", "properties": {"type": {"type": "string"}}}] + entry = {"allOf": schema_types} + for schema_key, vschema_type in ejs.typed_schemas[str(vschema)][0][0].items(): + schema_types.append( + { + "if": {"properties": {"type": {"const": schema_key}}}, + "then": get_jschema(f"{parent_key}-{schema_key}", vschema_type), + } + ) + + elif str(vschema) in ejs.hidden_schemas: + # get the schema from the automation schema + type = ejs.hidden_schemas[str(vschema)] + inner_vschema = vschema(ejs.jschema_extractor) + if type == "automation": + entry = get_automation_schema(parent_key, inner_vschema) + elif type == "maybe": + entry = get_jschema(parent_key, inner_vschema) + else: + raise ValueError("Unknown extracted schema type") + elif str(vschema).startswith(" 0 + # Here are schemas for pcf8574, mcp23xxx and other port expanders which add + # gpio registers + # ESPHome validates pins schemas if it founds a key in the pin configuration. + # This key is added to a required in jsonschema, and all options are part of a + # oneOf section, so only one is selected. Also internal schema adds number as required. + + for mode in ("INPUT", "OUTPUT"): + schema_name = f"PIN.GPIO_FULL_{mode}_PIN_SCHEMA" + internal = definitions[schema_name] + definitions[schema_name]["additionalItems"] = False + definitions[f"PIN.{mode}_INTERNAL"] = internal + schemas = [get_ref(f"PIN.{mode}_INTERNAL")] + schemas[0]["required"] = ["number"] + # accept string and object, for internal shorthand pin IO: + definitions[schema_name] = {"oneOf": schemas, "type": ["string", "object"]} + + for k, v in pin_registry.items(): + pin_jschema = get_jschema( + f"PIN.{mode}_" + k, v[1][0 if mode == "OUTPUT" else 1] + ) + if unref(pin_jschema): + pin_jschema["required"] = [k] + schemas.append(pin_jschema) + + +def dump_schema(): + import esphome.config_validation as cv + + from esphome import automation + from esphome.automation import validate_potentially_and_condition + from esphome import pins + from esphome.core import CORE + from esphome.helpers import write_file_if_changed + from esphome.components import remote_base + + # The root directory of the repo + root = Path(__file__).parent.parent + + # Fake some diretory so that get_component works + CORE.config_path = str(root) + + file_path = args.output + + schema_registry[cv.boolean] = {"type": "boolean"} + + for v in [ + cv.int_, + cv.int_range, + cv.positive_int, + cv.float_, + cv.positive_float, + cv.positive_float, + cv.positive_not_null_int, + cv.negative_one_to_one_float, + cv.port, + ]: + schema_registry[v] = {"type": "number"} + + for v in [ + cv.string, + cv.string_strict, + cv.valid_name, + cv.hex_int, + cv.hex_int_range, + pins.output_pin, + pins.input_pin, + pins.input_pullup_pin, + cv.subscribe_topic, + cv.publish_topic, + cv.mqtt_payload, + cv.ssid, + cv.percentage_int, + cv.percentage, + cv.possibly_negative_percentage, + cv.positive_time_period, + cv.positive_time_period_microseconds, + cv.positive_time_period_milliseconds, + cv.positive_time_period_minutes, + cv.positive_time_period_seconds, + ]: + schema_registry[v] = {"type": "string"} + + schema_registry[validate_potentially_and_condition] = get_ref("condition_list") + + for v in [pins.gpio_input_pin_schema, pins.gpio_input_pullup_pin_schema]: + schema_registry[v] = get_ref("PIN.GPIO_FULL_INPUT_PIN_SCHEMA") + + for v in [pins.gpio_output_pin_schema, pins.internal_gpio_output_pin_schema]: + schema_registry[v] = get_ref("PIN.GPIO_FULL_OUTPUT_PIN_SCHEMA") + + add_module_schemas("CONFIG", cv) + get_jschema("POLLING_COMPONENT", cv.polling_component_schema("60s")) + + add_pin_schema() + + add_module_schemas("REMOTE_BASE", remote_base) + add_module_schemas("AUTOMATION", automation) + + load_components() + add_registries() + + definitions["condition_list"] = { + JSC_ONEOF: [ + {"type": "array", "items": get_ref(JSC_CONDITION)}, + get_ref(JSC_CONDITION), + ] + } + + output = { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "definitions": definitions, + JSC_PROPERTIES: base_props, + } + + add_core() + add_buses() + add_components() + + add_registries() # need second pass, e.g. climate.pid.autotune + add_pin_registry() + solve_pending_refs() + + write_file_if_changed(file_path, json.dumps(output)) + print(f"Wrote {file_path}") + + +dump_schema() diff --git a/script/bump-docker-base-version.py b/script/bump-docker-base-version.py index 178643cea659..765a330ce4e7 100755 --- a/script/bump-docker-base-version.py +++ b/script/bump-docker-base-version.py @@ -17,36 +17,32 @@ def sub(path, pattern, repl, expected_count=1): def write_version(version: str): for p in [ - ".github/workflows/ci-docker.yml", - ".github/workflows/release-dev.yml", - ".github/workflows/release.yml" + ".github/workflows/ci-docker.yml", + ".github/workflows/release-dev.yml", + ".github/workflows/release.yml", ]: - sub( - p, - r'base_version=".*"', - f'base_version="{version}"' - ) + sub(p, r'base_version=".*"', f'base_version="{version}"') sub( "docker/Dockerfile", r"ARG BUILD_FROM=esphome/esphome-base-amd64:.*", - f"ARG BUILD_FROM=esphome/esphome-base-amd64:{version}" + f"ARG BUILD_FROM=esphome/esphome-base-amd64:{version}", ) sub( "docker/Dockerfile.dev", r"FROM esphome/esphome-base-amd64:.*", - f"FROM esphome/esphome-base-amd64:{version}" + f"FROM esphome/esphome-base-amd64:{version}", ) sub( "docker/Dockerfile.lint", r"FROM esphome/esphome-lint-base:.*", - f"FROM esphome/esphome-lint-base:{version}" + f"FROM esphome/esphome-lint-base:{version}", ) def main(): parser = argparse.ArgumentParser() - parser.add_argument('new_version', type=str) + parser.add_argument("new_version", type=str) args = parser.parse_args() version = args.new_version diff --git a/script/bump-version.py b/script/bump-version.py index 56062ac5cfd5..b7b048eb22b9 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -16,30 +16,27 @@ class Version: dev: bool = False def __str__(self): - return f'{self.major}.{self.minor}.{self.full_patch}' + return f"{self.major}.{self.minor}.{self.full_patch}" @property def full_patch(self): - res = f'{self.patch}' + res = f"{self.patch}" if self.beta > 0: - res += f'b{self.beta}' + res += f"b{self.beta}" if self.dev: - res += '-dev' + res += "-dev" return res @classmethod def parse(cls, value): - match = re.match(r'(\d+).(\d+).(\d+)(b\d+)?(-dev)?', value) + match = re.match(r"(\d+).(\d+).(\d+)(b\d+)?(-dev)?", value) assert match is not None major = int(match[1]) minor = int(match[2]) patch = int(match[3]) beta = int(match[4][1:]) if match[4] else 0 dev = bool(match[5]) - return Version( - major=major, minor=minor, patch=patch, - beta=beta, dev=dev - ) + return Version(major=major, minor=minor, patch=patch, beta=beta, dev=dev) def sub(path, pattern, repl, expected_count=1): @@ -54,25 +51,21 @@ def sub(path, pattern, repl, expected_count=1): def write_version(version: Version): sub( - 'esphome/const.py', - r"^MAJOR_VERSION = \d+$", - f"MAJOR_VERSION = {version.major}" + "esphome/const.py", r"^MAJOR_VERSION = \d+$", f"MAJOR_VERSION = {version.major}" ) sub( - 'esphome/const.py', - r"^MINOR_VERSION = \d+$", - f"MINOR_VERSION = {version.minor}" + "esphome/const.py", r"^MINOR_VERSION = \d+$", f"MINOR_VERSION = {version.minor}" ) sub( - 'esphome/const.py', + "esphome/const.py", r"^PATCH_VERSION = .*$", - f"PATCH_VERSION = '{version.full_patch}'" + f'PATCH_VERSION = "{version.full_patch}"', ) def main(): parser = argparse.ArgumentParser() - parser.add_argument('new_version', type=str) + parser.add_argument("new_version", type=str) args = parser.parse_args() version = Version.parse(args.new_version) diff --git a/script/ci-custom.py b/script/ci-custom.py index ab2beadf858e..3106cabeff42 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -14,6 +14,7 @@ sys.path.append(os.path.dirname(__file__)) from helpers import git_ls_files, filter_changed + def find_all(a_str, sub): if not a_str.find(sub): # Optimization: If str is not in whole text, then do not try @@ -30,18 +31,21 @@ def find_all(a_str, sub): parser = argparse.ArgumentParser() -parser.add_argument('files', nargs='*', default=[], - help='files to be processed (regex on path)') -parser.add_argument('-c', '--changed', action='store_true', - help='Only run on changed files') -parser.add_argument('--print-slowest', action='store_true', - help='Print the slowest checks') +parser.add_argument( + "files", nargs="*", default=[], help="files to be processed (regex on path)" +) +parser.add_argument( + "-c", "--changed", action="store_true", help="Only run on changed files" +) +parser.add_argument( + "--print-slowest", action="store_true", help="Print the slowest checks" +) args = parser.parse_args() EXECUTABLE_BIT = git_ls_files() files = list(EXECUTABLE_BIT.keys()) # Match against re -file_name_re = re.compile('|'.join(args.files)) +file_name_re = re.compile("|".join(args.files)) files = [p for p in files if file_name_re.search(p)] if args.changed: @@ -49,11 +53,32 @@ def find_all(a_str, sub): files.sort() -file_types = ('.h', '.c', '.cpp', '.tcc', '.yaml', '.yml', '.ini', '.txt', '.ico', '.svg', - '.py', '.html', '.js', '.md', '.sh', '.css', '.proto', '.conf', '.cfg', - '.woff', '.woff2', '') -cpp_include = ('*.h', '*.c', '*.cpp', '*.tcc') -ignore_types = ('.ico', '.woff', '.woff2', '') +file_types = ( + ".h", + ".c", + ".cpp", + ".tcc", + ".yaml", + ".yml", + ".ini", + ".txt", + ".ico", + ".svg", + ".py", + ".html", + ".js", + ".md", + ".sh", + ".css", + ".proto", + ".conf", + ".cfg", + ".woff", + ".woff2", + "", +) +cpp_include = ("*.h", "*.c", "*.cpp", "*.tcc") +ignore_types = (".ico", ".woff", ".woff2", "") LINT_FILE_CHECKS = [] LINT_CONTENT_CHECKS = [] @@ -61,9 +86,9 @@ def find_all(a_str, sub): def run_check(lint_obj, fname, *args): - include = lint_obj['include'] - exclude = lint_obj['exclude'] - func = lint_obj['func'] + include = lint_obj["include"] + exclude = lint_obj["exclude"] + func = lint_obj["func"] if include is not None: for incl in include: if fnmatch.fnmatch(fname, incl): @@ -85,21 +110,24 @@ def run_checks(lints, fname, *args): print(f"Check {lint['func'].__name__} on file {fname} failed:") raise duration = time.process_time() - start - lint.setdefault('durations', []).append(duration) + lint.setdefault("durations", []).append(duration) def _add_check(checks, func, include=None, exclude=None): - checks.append({ - 'include': include, - 'exclude': exclude or [], - 'func': func, - }) + checks.append( + { + "include": include, + "exclude": exclude or [], + "func": func, + } + ) def lint_file_check(**kwargs): def decorator(func): _add_check(LINT_FILE_CHECKS, func, **kwargs) return func + return decorator @@ -107,6 +135,7 @@ def lint_content_check(**kwargs): def decorator(func): _add_check(LINT_CONTENT_CHECKS, func, **kwargs) return func + return decorator @@ -116,7 +145,7 @@ def lint_post_check(func): def lint_re_check(regex, **kwargs): - flags = kwargs.pop('flags', re.MULTILINE) + flags = kwargs.pop("flags", re.MULTILINE) prog = re.compile(regex, flags) decor = lint_content_check(**kwargs) @@ -125,18 +154,19 @@ def decorator(func): def new_func(fname, content): errors = [] for match in prog.finditer(content): - if 'NOLINT' in match.group(0): + if "NOLINT" in match.group(0): continue lineno = content.count("\n", 0, match.start()) + 1 - substr = content[:match.start()] - col = len(substr) - substr.rfind('\n') + substr = content[: match.start()] + col = len(substr) - substr.rfind("\n") err = func(fname, match) if err is None: continue - errors.append((lineno, col+1, err)) + errors.append((lineno, col + 1, err)) return errors return decor(new_func) + return decorator @@ -152,73 +182,99 @@ def new_func(fname, content): errors = [] for line, col in find_all(content, find_): err = func(fname) - errors.append((line+1, col+1, err)) + errors.append((line + 1, col + 1, err)) return errors + return decor(new_func) + return decorator -@lint_file_check(include=['*.ino']) +@lint_file_check(include=["*.ino"]) def lint_ino(fname): return "This file extension (.ino) is not allowed. Please use either .cpp or .h" -@lint_file_check(exclude=[f'*{f}' for f in file_types] + [ - '.clang-*', '.dockerignore', '.editorconfig', '*.gitignore', 'LICENSE', 'pylintrc', - 'MANIFEST.in', 'docker/Dockerfile*', 'docker/rootfs/*', 'script/*', -]) +@lint_file_check( + exclude=[f"*{f}" for f in file_types] + + [ + ".clang-*", + ".dockerignore", + ".editorconfig", + "*.gitignore", + "LICENSE", + "pylintrc", + "MANIFEST.in", + "docker/Dockerfile*", + "docker/rootfs/*", + "script/*", + ] +) def lint_ext_check(fname): - return "This file extension is not a registered file type. If this is an error, please " \ - "update the script/ci-custom.py script." + return ( + "This file extension is not a registered file type. If this is an error, please " + "update the script/ci-custom.py script." + ) -@lint_file_check(exclude=[ - 'docker/rootfs/*', 'script/*', 'setup.py' -]) +@lint_file_check(exclude=["docker/rootfs/*", "docker/*.py", "script/*", "setup.py"]) def lint_executable_bit(fname): ex = EXECUTABLE_BIT[fname] if ex != 100644: - return 'File has invalid executable bit {}. If running from a windows machine please ' \ - 'see disabling executable bit in git.'.format(ex) + return ( + "File has invalid executable bit {}. If running from a windows machine please " + "see disabling executable bit in git.".format(ex) + ) return None -@lint_content_find_check('\t', exclude=[ - 'esphome/dashboard/static/ace.js', 'esphome/dashboard/static/ext-searchbox.js', -]) +@lint_content_find_check( + "\t", + exclude=[ + "esphome/dashboard/static/ace.js", + "esphome/dashboard/static/ext-searchbox.js", + ], +) def lint_tabs(fname): return "File contains tab character. Please convert tabs to spaces." -@lint_content_find_check('\r') +@lint_content_find_check("\r") def lint_newline(fname): return "File contains windows newline. Please set your editor to unix newline mode." -@lint_content_check(exclude=['*.svg']) +@lint_content_check(exclude=["*.svg"]) def lint_end_newline(fname, content): - if content and not content.endswith('\n'): + if content and not content.endswith("\n"): return "File does not end with a newline, please add an empty line at the end of the file." return None -CPP_RE_EOL = r'\s*?(?://.*?)?$' +CPP_RE_EOL = r"\s*?(?://.*?)?$" def highlight(s): - return f'\033[36m{s}\033[0m' + return f"\033[36m{s}\033[0m" -@lint_re_check(r'^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)' + CPP_RE_EOL, - include=cpp_include, exclude=['esphome/core/log.h']) +@lint_re_check( + r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL, + include=cpp_include, + exclude=["esphome/core/log.h"], +) def lint_no_defines(fname, match): - s = highlight('static const uint8_t {} = {};'.format(match.group(1), match.group(2))) - return ("#define macros for integer constants are not allowed, please use " - "{} style instead (replace uint8_t with the appropriate " - "datatype). See also Google style guide.".format(s)) + s = highlight( + "static const uint8_t {} = {};".format(match.group(1), match.group(2)) + ) + return ( + "#define macros for integer constants are not allowed, please use " + "{} style instead (replace uint8_t with the appropriate " + "datatype). See also Google style guide.".format(s) + ) -@lint_re_check(r'^\s*delay\((\d+)\);' + CPP_RE_EOL, include=cpp_include) +@lint_re_check(r"^\s*delay\((\d+)\);" + CPP_RE_EOL, include=cpp_include) def lint_no_long_delays(fname, match): duration_ms = int(match.group(1)) if duration_ms < 50: @@ -232,7 +288,7 @@ def lint_no_long_delays(fname, match): ) -@lint_content_check(include=['esphome/const.py']) +@lint_content_check(include=["esphome/const.py"]) def lint_const_ordered(fname, content): """Lint that value in const.py are ordered. @@ -240,54 +296,67 @@ def lint_const_ordered(fname, content): """ lines = content.splitlines() errors = [] - for start in ['CONF_', 'ICON_', 'UNIT_']: - matching = [(i+1, line) for i, line in enumerate(lines) if line.startswith(start)] - ordered = list(sorted(matching, key=lambda x: x[1].replace('_', ' '))) + for start in ["CONF_", "ICON_", "UNIT_"]: + matching = [ + (i + 1, line) for i, line in enumerate(lines) if line.startswith(start) + ] + ordered = list(sorted(matching, key=lambda x: x[1].replace("_", " "))) ordered = [(mi, ol) for (mi, _), (_, ol) in zip(matching, ordered)] for (mi, ml), (oi, ol) in zip(matching, ordered): if ml == ol: continue target = next(i for i, l in ordered if l == ml) target_text = next(l for i, l in matching if target == i) - errors.append((mi, 1, - f"Constant {highlight(ml)} is not ordered, please make sure all " - f"constants are ordered. See line {mi} (should go to line {target}, " - f"{target_text})")) + errors.append( + ( + mi, + 1, + f"Constant {highlight(ml)} is not ordered, please make sure all " + f"constants are ordered. See line {mi} (should go to line {target}, " + f"{target_text})", + ) + ) return errors -@lint_re_check(r'^\s*CONF_([A-Z_0-9a-z]+)\s+=\s+[\'"](.*?)[\'"]\s*?$', include=['*.py']) +@lint_re_check(r'^\s*CONF_([A-Z_0-9a-z]+)\s+=\s+[\'"](.*?)[\'"]\s*?$', include=["*.py"]) def lint_conf_matches(fname, match): const = match.group(1) value = match.group(2) const_norm = const.lower() - value_norm = value.replace('.', '_') + value_norm = value.replace(".", "_") if const_norm == value_norm: return None - return ("Constant {} does not match value {}! Please make sure the constant's name matches its " - "value!" - "".format(highlight('CONF_' + const), highlight(value))) + return ( + "Constant {} does not match value {}! Please make sure the constant's name matches its " + "value!" + "".format(highlight("CONF_" + const), highlight(value)) + ) CONF_RE = r'^(CONF_[a-zA-Z0-9_]+)\s*=\s*[\'"].*?[\'"]\s*?$' -with codecs.open('esphome/const.py', 'r', encoding='utf-8') as f_handle: +with codecs.open("esphome/const.py", "r", encoding="utf-8") as f_handle: constants_content = f_handle.read() CONSTANTS = [m.group(1) for m in re.finditer(CONF_RE, constants_content, re.MULTILINE)] CONSTANTS_USES = collections.defaultdict(list) -@lint_re_check(CONF_RE, include=['*.py'], exclude=['esphome/const.py']) +@lint_re_check(CONF_RE, include=["*.py"], exclude=["esphome/const.py"]) def lint_conf_from_const_py(fname, match): name = match.group(1) if name not in CONSTANTS: CONSTANTS_USES[name].append(fname) return None - return ("Constant {} has already been defined in const.py - please import the constant from " - "const.py directly.".format(highlight(name))) + return ( + "Constant {} has already been defined in const.py - please import the constant from " + "const.py directly.".format(highlight(name)) + ) -RAW_PIN_ACCESS_RE = r'^\s(pinMode|digitalWrite|digitalRead)\((.*)->get_pin\(\),\s*([^)]+).*\)' +RAW_PIN_ACCESS_RE = ( + r"^\s(pinMode|digitalWrite|digitalRead)\((.*)->get_pin\(\),\s*([^)]+).*\)" +) @lint_re_check(RAW_PIN_ACCESS_RE, include=cpp_include) @@ -296,33 +365,49 @@ def lint_no_raw_pin_access(fname, match): pin = match.group(2) mode = match.group(3) new_func = { - 'pinMode': 'pin_mode', - 'digitalWrite': 'digital_write', - 'digitalRead': 'digital_read', + "pinMode": "pin_mode", + "digitalWrite": "digital_write", + "digitalRead": "digital_read", }[func] - new_code = highlight(f'{pin}->{new_func}({mode})') - return (f"Don't use raw {func} calls. Instead, use the `->{new_func}` function: {new_code}") + new_code = highlight(f"{pin}->{new_func}({mode})") + return f"Don't use raw {func} calls. Instead, use the `->{new_func}` function: {new_code}" # Functions from Arduino framework that are forbidden to use directly ARDUINO_FORBIDDEN = [ - 'digitalWrite', 'digitalRead', 'pinMode', - 'shiftOut', 'shiftIn', - 'radians', 'degrees', - 'interrupts', 'noInterrupts', - 'lowByte', 'highByte', - 'bitRead', 'bitSet', 'bitClear', 'bitWrite', - 'bit', 'analogRead', 'analogWrite', - 'pulseIn', 'pulseInLong', - 'tone', + "digitalWrite", + "digitalRead", + "pinMode", + "shiftOut", + "shiftIn", + "radians", + "degrees", + "interrupts", + "noInterrupts", + "lowByte", + "highByte", + "bitRead", + "bitSet", + "bitClear", + "bitWrite", + "bit", + "analogRead", + "analogWrite", + "pulseIn", + "pulseInLong", + "tone", ] -ARDUINO_FORBIDDEN_RE = r'[^\w\d](' + r'|'.join(ARDUINO_FORBIDDEN) + r')\(.*' +ARDUINO_FORBIDDEN_RE = r"[^\w\d](" + r"|".join(ARDUINO_FORBIDDEN) + r")\(.*" -@lint_re_check(ARDUINO_FORBIDDEN_RE, include=cpp_include, exclude=[ - 'esphome/components/mqtt/custom_mqtt_device.h', - 'esphome/core/esphal.*', -]) +@lint_re_check( + ARDUINO_FORBIDDEN_RE, + include=cpp_include, + exclude=[ + "esphome/components/mqtt/custom_mqtt_device.h", + "esphome/core/esphal.*", + ], +) def lint_no_arduino_framework_functions(fname, match): nolint = highlight("// NOLINT") return ( @@ -334,9 +419,13 @@ def lint_no_arduino_framework_functions(fname, match): ) -@lint_re_check(r'[^\w\d]byte\s+[\w\d]+\s*=', include=cpp_include, exclude={ - 'esphome/components/tuya/tuya.h', -}) +@lint_re_check( + r"[^\w\d]byte\s+[\w\d]+\s*=", + include=cpp_include, + exclude={ + "esphome/components/tuya/tuya.h", + }, +) def lint_no_byte_datatype(fname, match): return ( f"The datatype {highlight('byte')} is not allowed to be used in ESPHome. " @@ -350,112 +439,143 @@ def lint_constants_usage(): for constant, uses in CONSTANTS_USES.items(): if len(uses) < 4: continue - errors.append("Constant {} is defined in {} files. Please move all definitions of the " - "constant to const.py (Uses: {})" - "".format(highlight(constant), len(uses), ', '.join(uses))) + errors.append( + "Constant {} is defined in {} files. Please move all definitions of the " + "constant to const.py (Uses: {})" + "".format(highlight(constant), len(uses), ", ".join(uses)) + ) return errors def relative_cpp_search_text(fname, content): - parts = fname.split('/') + parts = fname.split("/") integration = parts[2] return f'#include "esphome/components/{integration}' -@lint_content_find_check(relative_cpp_search_text, include=['esphome/components/*.cpp']) +@lint_content_find_check(relative_cpp_search_text, include=["esphome/components/*.cpp"]) def lint_relative_cpp_import(fname): - return ("Component contains absolute import - Components must always use " - "relative imports.\n" - "Change:\n" - ' #include "esphome/components/abc/abc.h"\n' - 'to:\n' - ' #include "abc.h"\n\n') + return ( + "Component contains absolute import - Components must always use " + "relative imports.\n" + "Change:\n" + ' #include "esphome/components/abc/abc.h"\n' + "to:\n" + ' #include "abc.h"\n\n' + ) def relative_py_search_text(fname, content): - parts = fname.split('/') + parts = fname.split("/") integration = parts[2] - return f'esphome.components.{integration}' + return f"esphome.components.{integration}" -@lint_content_find_check(relative_py_search_text, include=['esphome/components/*.py'], - exclude=['esphome/components/web_server/__init__.py']) +@lint_content_find_check( + relative_py_search_text, + include=["esphome/components/*.py"], + exclude=["esphome/components/web_server/__init__.py"], +) def lint_relative_py_import(fname): - return ("Component contains absolute import - Components must always use " - "relative imports within the integration.\n" - "Change:\n" - ' from esphome.components.abc import abc_ns"\n' - 'to:\n' - ' from . import abc_ns\n\n') + return ( + "Component contains absolute import - Components must always use " + "relative imports within the integration.\n" + "Change:\n" + ' from esphome.components.abc import abc_ns"\n' + "to:\n" + " from . import abc_ns\n\n" + ) -@lint_content_check(include=['esphome/components/*.h', 'esphome/components/*.cpp', - 'esphome/components/*.tcc']) +@lint_content_check( + include=[ + "esphome/components/*.h", + "esphome/components/*.cpp", + "esphome/components/*.tcc", + ] +) def lint_namespace(fname, content): - expected_name = re.match(r'^esphome/components/([^/]+)/.*', - fname.replace(os.path.sep, '/')).group(1) - search = f'namespace {expected_name}' + expected_name = re.match( + r"^esphome/components/([^/]+)/.*", fname.replace(os.path.sep, "/") + ).group(1) + search = f"namespace {expected_name}" if search in content: return None - return 'Invalid namespace found in C++ file. All integration C++ files should put all ' \ - 'functions in a separate namespace that matches the integration\'s name. ' \ - 'Please make sure the file contains {}'.format(highlight(search)) + return ( + "Invalid namespace found in C++ file. All integration C++ files should put all " + "functions in a separate namespace that matches the integration's name. " + "Please make sure the file contains {}".format(highlight(search)) + ) -@lint_content_find_check('"esphome.h"', include=cpp_include, exclude=['tests/custom.h']) +@lint_content_find_check('"esphome.h"', include=cpp_include, exclude=["tests/custom.h"]) def lint_esphome_h(fname): - return ("File contains reference to 'esphome.h' - This file is " - "auto-generated and should only be used for *custom* " - "components. Please replace with references to the direct files.") + return ( + "File contains reference to 'esphome.h' - This file is " + "auto-generated and should only be used for *custom* " + "components. Please replace with references to the direct files." + ) -@lint_content_check(include=['*.h']) +@lint_content_check(include=["*.h"]) def lint_pragma_once(fname, content): - if '#pragma once' not in content: - return ("Header file contains no 'pragma once' header guard. Please add a " - "'#pragma once' line at the top of the file.") + if "#pragma once" not in content: + return ( + "Header file contains no 'pragma once' header guard. Please add a " + "'#pragma once' line at the top of the file." + ) return None -@lint_re_check(r'(whitelist|blacklist|slave)', - exclude=['script/ci-custom.py'], flags=re.IGNORECASE | re.MULTILINE) +@lint_re_check( + r"(whitelist|blacklist|slave)", + exclude=["script/ci-custom.py"], + flags=re.IGNORECASE | re.MULTILINE, +) def lint_inclusive_language(fname, match): # From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=49decddd39e5f6132ccd7d9fdc3d7c470b0061bb - return ("Avoid the use of whitelist/blacklist/slave.\n" - "Recommended replacements for 'master / slave' are:\n" - " '{primary,main} / {secondary,replica,subordinate}\n" - " '{initiator,requester} / {target,responder}'\n" - " '{controller,host} / {device,worker,proxy}'\n" - " 'leader / follower'\n" - " 'director / performer'\n" - "\n" - "Recommended replacements for 'blacklist/whitelist' are:\n" - " 'denylist / allowlist'\n" - " 'blocklist / passlist'") - - - -@lint_content_find_check('ESP_LOG', include=['*.h', '*.tcc'], exclude=[ - 'esphome/components/binary_sensor/binary_sensor.h', - 'esphome/components/cover/cover.h', - 'esphome/components/display/display_buffer.h', - 'esphome/components/i2c/i2c.h', - 'esphome/components/mqtt/mqtt_component.h', - 'esphome/components/output/binary_output.h', - 'esphome/components/output/float_output.h', - 'esphome/components/sensor/sensor.h', - 'esphome/components/stepper/stepper.h', - 'esphome/components/switch/switch.h', - 'esphome/components/text_sensor/text_sensor.h', - 'esphome/components/climate/climate.h', - 'esphome/core/component.h', - 'esphome/core/esphal.h', - 'esphome/core/log.h', - 'tests/custom.h', -]) + return ( + "Avoid the use of whitelist/blacklist/slave.\n" + "Recommended replacements for 'master / slave' are:\n" + " '{primary,main} / {secondary,replica,subordinate}\n" + " '{initiator,requester} / {target,responder}'\n" + " '{controller,host} / {device,worker,proxy}'\n" + " 'leader / follower'\n" + " 'director / performer'\n" + "\n" + "Recommended replacements for 'blacklist/whitelist' are:\n" + " 'denylist / allowlist'\n" + " 'blocklist / passlist'" + ) + + +@lint_content_find_check( + "ESP_LOG", + include=["*.h", "*.tcc"], + exclude=[ + "esphome/components/binary_sensor/binary_sensor.h", + "esphome/components/cover/cover.h", + "esphome/components/display/display_buffer.h", + "esphome/components/i2c/i2c.h", + "esphome/components/mqtt/mqtt_component.h", + "esphome/components/output/binary_output.h", + "esphome/components/output/float_output.h", + "esphome/components/sensor/sensor.h", + "esphome/components/stepper/stepper.h", + "esphome/components/switch/switch.h", + "esphome/components/text_sensor/text_sensor.h", + "esphome/components/climate/climate.h", + "esphome/core/component.h", + "esphome/core/esphal.h", + "esphome/core/log.h", + "tests/custom.h", + ], +) def lint_log_in_header(fname): - return ('Found reference to ESP_LOG in header file. Using ESP_LOG* in header files ' - 'is currently not possible - please move the definition to a source file (.cpp)') + return ( + "Found reference to ESP_LOG in header file. Using ESP_LOG* in header files " + "is currently not possible - please move the definition to a source file (.cpp)" + ) errors = collections.defaultdict(list) @@ -488,14 +608,17 @@ def add_errors(fname, errs): if ext in ignore_types: continue try: - with codecs.open(fname, 'r', encoding='utf-8') as f_handle: + with codecs.open(fname, "r", encoding="utf-8") as f_handle: content = f_handle.read() except UnicodeDecodeError: - add_errors(fname, "File is not readable as UTF-8. Please set your editor to UTF-8 mode.") + add_errors( + fname, + "File is not readable as UTF-8. Please set your editor to UTF-8 mode.", + ) continue run_checks(LINT_CONTENT_CHECKS, fname, fname, content) -run_checks(LINT_POST_CHECKS, 'POST') +run_checks(LINT_POST_CHECKS, "POST") for f, errs in sorted(errors.items()): print(f"\033[0;32m************* File \033[1;32m{f}\033[0m") @@ -506,8 +629,8 @@ def add_errors(fname, errs): if args.print_slowest: lint_times = [] for lint in LINT_FILE_CHECKS + LINT_CONTENT_CHECKS + LINT_POST_CHECKS: - durations = lint.get('durations', []) - lint_times.append((sum(durations), len(durations), lint['func'].__name__)) + durations = lint.get("durations", []) + lint_times.append((sum(durations), len(durations), lint["func"].__name__)) lint_times.sort(key=lambda x: -x[0]) for i in range(min(len(lint_times), 10)): dur, invocations, name = lint_times[i] diff --git a/script/component_test b/script/component_test new file mode 100755 index 000000000000..549c68fb254f --- /dev/null +++ b/script/component_test @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +set -x + +pytest tests/component_tests diff --git a/script/fulltest b/script/fulltest index 795482281a61..a605beebfeba 100755 --- a/script/fulltest +++ b/script/fulltest @@ -10,4 +10,5 @@ script/ci-custom.py script/lint-python script/lint-cpp script/unit_test +script/component_test script/test diff --git a/script/helpers.py b/script/helpers.py index e0aaee87115e..5b1b7ba9180b 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -5,15 +5,15 @@ import subprocess import sys -root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..'))) -basepath = os.path.join(root_path, 'esphome') -temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp') +root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, "..", ".."))) +basepath = os.path.join(root_path, "esphome") +temp_header_file = os.path.join(root_path, ".temp-clang-tidy.cpp") def shlex_quote(s): if not s: return "''" - if re.search(r'[^\w@%+=:,./-]', s) is None: + if re.search(r"[^\w@%+=:,./-]", s) is None: return s return "'" + s.replace("'", "'\"'\"'") + "'" @@ -24,63 +24,71 @@ def build_all_include(): # Otherwise header-only integrations would not be tested by clang-tidy headers = [] for path in walk_files(basepath): - filetypes = ('.h',) + filetypes = (".h",) ext = os.path.splitext(path)[1] if ext in filetypes: path = os.path.relpath(path, root_path) - include_p = path.replace(os.path.sep, '/') + include_p = path.replace(os.path.sep, "/") headers.append(f'#include "{include_p}"') headers.sort() - headers.append('') - content = '\n'.join(headers) - with codecs.open(temp_header_file, 'w', encoding='utf-8') as f: + headers.append("") + content = "\n".join(headers) + with codecs.open(temp_header_file, "w", encoding="utf-8") as f: f.write(content) def build_compile_commands(): - gcc_flags_json = os.path.join(root_path, '.gcc-flags.json') + gcc_flags_json = os.path.join(root_path, ".gcc-flags.json") if not os.path.isfile(gcc_flags_json): print("Could not find {} file which is required for clang-tidy.") - print('Please run "pio init --ide atom" in the root esphome folder to generate that file.') + print( + 'Please run "pio init --ide atom" in the root esphome folder to generate that file.' + ) sys.exit(1) - with codecs.open(gcc_flags_json, 'r', encoding='utf-8') as f: + with codecs.open(gcc_flags_json, "r", encoding="utf-8") as f: gcc_flags = json.load(f) - exec_path = gcc_flags['execPath'] - include_paths = gcc_flags['gccIncludePaths'].split(',') - includes = [f'-I{p}' for p in include_paths] - cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ') - defines = [flag for flag in cpp_flags if flag.startswith('-D')] + exec_path = gcc_flags["execPath"] + include_paths = gcc_flags["gccIncludePaths"].split(",") + includes = [f"-I{p}" for p in include_paths] + cpp_flags = gcc_flags["gccDefaultCppFlags"].split(" ") + defines = [flag for flag in cpp_flags if flag.startswith("-D")] command = [exec_path] command.extend(includes) command.extend(defines) - command.append('-std=gnu++11') - command.append('-Wall') - command.append('-Wno-delete-non-virtual-dtor') - command.append('-Wno-unused-variable') - command.append('-Wunreachable-code') + command.append("-std=gnu++11") + command.append("-Wall") + command.append("-Wno-delete-non-virtual-dtor") + command.append("-Wno-unused-variable") + command.append("-Wunreachable-code") source_files = [] for path in walk_files(basepath): - filetypes = ('.cpp',) + filetypes = (".cpp",) ext = os.path.splitext(path)[1] if ext in filetypes: source_files.append(os.path.abspath(path)) source_files.append(temp_header_file) source_files.sort() - compile_commands = [{ - 'directory': root_path, - 'command': ' '.join(shlex_quote(x) for x in (command + ['-o', p + '.o', '-c', p])), - 'file': p - } for p in source_files] - compile_commands_json = os.path.join(root_path, 'compile_commands.json') + compile_commands = [ + { + "directory": root_path, + "command": " ".join( + shlex_quote(x) for x in (command + ["-o", p + ".o", "-c", p]) + ), + "file": p, + } + for p in source_files + ] + compile_commands_json = os.path.join(root_path, "compile_commands.json") if os.path.isfile(compile_commands_json): - with codecs.open(compile_commands_json, 'r', encoding='utf-8') as f: + with codecs.open(compile_commands_json, "r", encoding="utf-8") as f: try: if json.load(f) == compile_commands: return + # pylint: disable=bare-except except: pass - with codecs.open(compile_commands_json, 'w', encoding='utf-8') as f: + with codecs.open(compile_commands_json, "w", encoding="utf-8") as f: json.dump(compile_commands, f, indent=2) @@ -93,7 +101,13 @@ def walk_files(path): def get_output(*args): proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, err = proc.communicate() - return output.decode('utf-8') + return output.decode("utf-8") + + +def get_err(*args): + proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, err = proc.communicate() + return err.decode("utf-8") def splitlines_no_ends(string): @@ -101,18 +115,19 @@ def splitlines_no_ends(string): def changed_files(): - check_remotes = ['upstream', 'origin'] - check_remotes.extend(splitlines_no_ends(get_output('git', 'remote'))) + check_remotes = ["upstream", "origin"] + check_remotes.extend(splitlines_no_ends(get_output("git", "remote"))) for remote in check_remotes: - command = ['git', 'merge-base', f'refs/remotes/{remote}/dev', 'HEAD'] + command = ["git", "merge-base", f"refs/remotes/{remote}/dev", "HEAD"] try: merge_base = splitlines_no_ends(get_output(*command))[0] break + # pylint: disable=bare-except except: pass else: raise ValueError("Git not configured") - command = ['git', 'diff', merge_base, '--name-only'] + command = ["git", "diff", merge_base, "--name-only"] changed = splitlines_no_ends(get_output(*command)) changed = [os.path.relpath(f, os.getcwd()) for f in changed] changed.sort() @@ -131,10 +146,8 @@ def filter_changed(files): def git_ls_files(): - command = ['git', 'ls-files', '-s'] + command = ["git", "ls-files", "-s"] proc = subprocess.Popen(command, stdout=subprocess.PIPE) output, err = proc.communicate() - lines = [x.split() for x in output.decode('utf-8').splitlines()] - return { - s[3].strip(): int(s[0]) for s in lines - } + lines = [x.split() for x in output.decode("utf-8").splitlines()] + return {s[3].strip(): int(s[0]) for s in lines} diff --git a/script/lint-python b/script/lint-python index 49151152628b..41885b967230 100755 --- a/script/lint-python +++ b/script/lint-python @@ -1,15 +1,14 @@ #!/usr/bin/env python3 from __future__ import print_function +from helpers import get_output, get_err, git_ls_files, filter_changed import argparse -import collections import os import re import sys sys.path.append(os.path.dirname(__file__)) -from helpers import get_output, git_ls_files, filter_changed curfile = None @@ -22,26 +21,28 @@ def print_error(file, lineno, msg): print("\033[0;32m************* File \033[1;32m{}\033[0m".format(file)) curfile = file - print(u'{}:{} - {}'.format(file, lineno, msg)) + print("{}:{} - {}".format(file, lineno, msg)) def main(): parser = argparse.ArgumentParser() - parser.add_argument('files', nargs='*', default=[], - help='files to be processed (regex on path)') - parser.add_argument('-c', '--changed', action='store_true', - help='Only run on changed files') + parser.add_argument( + "files", nargs="*", default=[], help="files to be processed (regex on path)" + ) + parser.add_argument( + "-c", "--changed", action="store_true", help="Only run on changed files" + ) args = parser.parse_args() files = [] for path in git_ls_files(): - filetypes = ('.py',) + filetypes = (".py",) ext = os.path.splitext(path)[1] - if ext in filetypes and path.startswith('esphome'): + if ext in filetypes and path.startswith("esphome"): path = os.path.relpath(path, os.getcwd()) files.append(path) # Match against re - file_name_re = re.compile('|'.join(args.files)) + file_name_re = re.compile("|".join(args.files)) files = [p for p in files if file_name_re.search(p)] if args.changed: @@ -52,34 +53,45 @@ def main(): sys.exit(0) errors = 0 - cmd = ['flake8'] + files + + cmd = ["black", "--verbose", "--check"] + files + print("Running black...") + log = get_err(*cmd) + for line in log.splitlines(): + WOULD_REFORMAT = "would reformat" + if line.startswith(WOULD_REFORMAT): + file_ = line[len(WOULD_REFORMAT) + 1 :] + print_error(file_, None, "Please format this file with the black formatter") + errors += 1 + + cmd = ["flake8"] + files print("Running flake8...") log = get_output(*cmd) for line in log.splitlines(): - line = line.split(':', 4) + line = line.split(":", 4) if len(line) < 4: continue file_ = line[0] linno = line[1] - msg = (':'.join(line[3:])).strip() + msg = (":".join(line[3:])).strip() print_error(file_, linno, msg) errors += 1 - cmd = ['pylint', '-f', 'parseable', '--persistent=n'] + files + cmd = ["pylint", "-f", "parseable", "--persistent=n"] + files print("Running pylint...") log = get_output(*cmd) for line in log.splitlines(): - line = line.split(':', 3) + line = line.split(":", 3) if len(line) < 3: continue file_ = line[0] linno = line[1] - msg = (':'.join(line[2:])).strip() + msg = (":".join(line[2:])).strip() print_error(file_, linno, msg) errors += 1 sys.exit(errors) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/script/setup b/script/setup index d70a44ee493b..199b46891d14 100755 --- a/script/setup +++ b/script/setup @@ -6,3 +6,5 @@ set -e cd "$(dirname "$0")/.." pip3 install -r requirements.txt -r requirements_test.txt pip3 install -e . + +pre-commit install diff --git a/setup.cfg b/setup.cfg index 32a60839a5a4..755cef47c00f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,45 @@ Topic :: Home Automation [flake8] max-line-length = 120 +# Following 4 for black compatibility +# E501: line too long +# W503: Line break occurred before a binary operator +# E203: Whitespace before ':' +# D202 No blank lines allowed after function docstring + +# TODO fix flake8 +# D100 Missing docstring in public module +# D101 Missing docstring in public class +# D102 Missing docstring in public method +# D103 Missing docstring in public function +# D104 Missing docstring in public package +# D105 Missing docstring in magic method +# D107 Missing docstring in __init__ +# D200 One-line docstring should fit on one line with quotes +# D205 1 blank line required between summary line and description +# D209 Multi-line docstring closing quotes should be on a separate line +# D400 First line should end with a period +# D401 First line should be in imperative mood + +ignore = + E501, + W503, + E203, + D202, + + D100, + D101, + D102, + D103, + D104, + D105, + D107, + D200, + D205, + D209, + D400, + D401, + exclude = api_pb2.py [bdist_wheel] diff --git a/setup.py b/setup.py index 20435b86b5c0..3b4b77d61e1a 100755 --- a/setup.py +++ b/setup.py @@ -6,49 +6,50 @@ from esphome import const -PROJECT_NAME = 'esphome' -PROJECT_PACKAGE_NAME = 'esphome' -PROJECT_LICENSE = 'MIT' -PROJECT_AUTHOR = 'ESPHome' -PROJECT_COPYRIGHT = '2019, ESPHome' -PROJECT_URL = 'https://esphome.io/' -PROJECT_EMAIL = 'contact@esphome.io' +PROJECT_NAME = "esphome" +PROJECT_PACKAGE_NAME = "esphome" +PROJECT_LICENSE = "MIT" +PROJECT_AUTHOR = "ESPHome" +PROJECT_COPYRIGHT = "2019, ESPHome" +PROJECT_URL = "https://esphome.io/" +PROJECT_EMAIL = "contact@esphome.io" -PROJECT_GITHUB_USERNAME = 'esphome' -PROJECT_GITHUB_REPOSITORY = 'esphome' +PROJECT_GITHUB_USERNAME = "esphome" +PROJECT_GITHUB_REPOSITORY = "esphome" -PYPI_URL = 'https://pypi.python.org/pypi/{}'.format(PROJECT_PACKAGE_NAME) -GITHUB_PATH = '{}/{}'.format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY) -GITHUB_URL = 'https://github.com/{}'.format(GITHUB_PATH) +PYPI_URL = "https://pypi.python.org/pypi/{}".format(PROJECT_PACKAGE_NAME) +GITHUB_PATH = "{}/{}".format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY) +GITHUB_URL = "https://github.com/{}".format(GITHUB_PATH) -DOWNLOAD_URL = '{}/archive/v{}.zip'.format(GITHUB_URL, const.__version__) +DOWNLOAD_URL = "{}/archive/v{}.zip".format(GITHUB_URL, const.__version__) here = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(here, 'requirements.txt')) as requirements_txt: +with open(os.path.join(here, "requirements.txt")) as requirements_txt: REQUIRES = requirements_txt.read().splitlines() -with open(os.path.join(here, 'README.md')) as readme: +with open(os.path.join(here, "README.md")) as readme: LONG_DESCRIPTION = readme.read() # If you have problems importing platformio and esptool as modules you can set # $ESPHOME_USE_SUBPROCESS to make ESPHome call their executables instead. # This means they have to be in your $PATH. -if 'ESPHOME_USE_SUBPROCESS' in os.environ: +if "ESPHOME_USE_SUBPROCESS" in os.environ: # Remove platformio and esptool from requirements REQUIRES = [ - req for req in REQUIRES - if not any(req.startswith(prefix) for prefix in ['platformio', 'esptool']) + req + for req in REQUIRES + if not any(req.startswith(prefix) for prefix in ["platformio", "esptool"]) ] CLASSIFIERS = [ - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: End Users/Desktop', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: C++', - 'Programming Language :: Python :: 3', - 'Topic :: Home Automation', + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: MIT License", + "Programming Language :: C++", + "Programming Language :: Python :: 3", + "Topic :: Home Automation", ] setup( @@ -68,18 +69,14 @@ author_email=PROJECT_EMAIL, description="Make creating custom firmwares for ESP32/ESP8266 super easy.", long_description=LONG_DESCRIPTION, - long_description_content_type='text/markdown', + long_description_content_type="text/markdown", include_package_data=True, zip_safe=False, - platforms='any', - test_suite='tests', - python_requires='>=3.6,<4.0', + platforms="any", + test_suite="tests", + python_requires=">=3.6,<4.0", install_requires=REQUIRES, - keywords=['home', 'automation'], - entry_points={ - 'console_scripts': [ - 'esphome = esphome.__main__:main' - ] - }, - packages=find_packages(include="esphome.*") + keywords=["home", "automation"], + entry_points={"console_scripts": ["esphome = esphome.__main__:main"]}, + packages=find_packages(include="esphome.*"), ) diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.py b/tests/component_tests/binary_sensor/test_binary_sensor.py index 72c0dc1cdedc..8da93a476ecb 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.py +++ b/tests/component_tests/binary_sensor/test_binary_sensor.py @@ -1,4 +1,4 @@ -""" Tests for the binary sensor component """ +"""Tests for the binary sensor component.""" def test_binary_sensor_is_setup(generate_main): @@ -8,7 +8,9 @@ def test_binary_sensor_is_setup(generate_main): # Given # When - main_cpp = generate_main("tests/component_tests/binary_sensor/test_binary_sensor.yaml") + main_cpp = generate_main( + "tests/component_tests/binary_sensor/test_binary_sensor.yaml" + ) # Then assert "new gpio::GPIOBinarySensor();" in main_cpp @@ -22,10 +24,12 @@ def test_binary_sensor_sets_mandatory_fields(generate_main): # Given # When - main_cpp = generate_main("tests/component_tests/binary_sensor/test_binary_sensor.yaml") + main_cpp = generate_main( + "tests/component_tests/binary_sensor/test_binary_sensor.yaml" + ) # Then - assert "bs_1->set_name(\"test bs1\");" in main_cpp + assert 'bs_1->set_name("test bs1");' in main_cpp assert "bs_1->set_pin(new GPIOPin" in main_cpp @@ -36,7 +40,9 @@ def test_binary_sensor_config_value_internal_set(generate_main): # Given # When - main_cpp = generate_main("tests/component_tests/binary_sensor/test_binary_sensor.yaml") + main_cpp = generate_main( + "tests/component_tests/binary_sensor/test_binary_sensor.yaml" + ) # Then assert "bs_1->set_internal(true);" in main_cpp diff --git a/tests/component_tests/conftest.py b/tests/component_tests/conftest.py index 207a6dead928..aa564ed7b1c8 100644 --- a/tests/component_tests/conftest.py +++ b/tests/component_tests/conftest.py @@ -1,4 +1,12 @@ -""" Fixtures for component tests """ +"""Fixtures for component tests.""" + +import sys +from pathlib import Path + +# Add package root to python path +here = Path(__file__).parent +package_root = here.parent.parent +sys.path.insert(0, package_root.as_posix()) import pytest @@ -9,7 +17,7 @@ @pytest.fixture def generate_main(): - """ Generates the C++ main.cpp file and returns it in string form """ + """Generates the C++ main.cpp file and returns it in string form.""" def generator(path: str) -> str: CORE.config_path = path diff --git a/tests/component_tests/sensor/test_sensor.py b/tests/component_tests/sensor/test_sensor.py new file mode 100644 index 000000000000..35ce1f4e11b0 --- /dev/null +++ b/tests/component_tests/sensor/test_sensor.py @@ -0,0 +1,14 @@ +"""Tests for the sensor component.""" + + +def test_sensor_device_class_set(generate_main): + """ + When the device_class of sensor is set in the yaml file, it should be registered in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/sensor/test_sensor.yaml") + + # Then + assert 's_1->set_device_class("voltage");' in main_cpp diff --git a/tests/component_tests/sensor/test_sensor.yaml b/tests/component_tests/sensor/test_sensor.yaml new file mode 100644 index 000000000000..a38dd14041de --- /dev/null +++ b/tests/component_tests/sensor/test_sensor.yaml @@ -0,0 +1,12 @@ +esphome: + name: test + platform: ESP8266 + board: d1_mini_lite + +sensor: + - platform: adc + pin: A0 + id: s_1 + name: "test s1" + update_interval: 60s + device_class: "voltage" diff --git a/tests/test1.yaml b/tests/test1.yaml index f274defc4661..e39b8d513f62 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1,8 +1,12 @@ substitutions: devicename: test1 + sensorname: my + textname: template + roomname: living_room esphome: name: test1 + name_add_mac_suffix: true platform: ESP32 board: nodemcu-32s on_boot: @@ -26,6 +30,31 @@ esphome: green: !lambda 'return 255;' blue: 0% white: 100% + - http_request.get: + url: https://esphome.io + headers: + Content-Type: application/json + verify_ssl: false + - http_request.post: + url: https://esphome.io + verify_ssl: false + json: + key: !lambda |- + return id(${textname}_text).state; + greeting: 'Hello World' + - http_request.send: + method: PUT + url: https://esphome.io + headers: + Content-Type: application/json + body: 'Some data' + verify_ssl: false + on_response: + then: + - logger.log: + format: 'Response status: %d' + args: + - status_code build_path: build/test1 packages: @@ -50,6 +79,10 @@ wifi: reboot_timeout: 120s power_save_mode: none +http_request: + useragent: esphome/device + timeout: 10s + mqtt: broker: '192.168.178.84' port: 1883 @@ -98,7 +131,7 @@ mqtt: int data = x["my_data"]; ESP_LOGD("main", "The data is: %d", data); - light.turn_on: - id: living_room_lights + id: ${roomname}_lights brightness: !lambda |- float brightness = 1.0; if (x.containsKey("brightness")) @@ -110,17 +143,21 @@ mqtt: effect = x["effect"]; return effect; - light.control: - id: living_room_lights - brightness: !lambda 'return id(living_room_lights).current_values.get_brightness() + 0.5;' + id: ${roomname}_lights + brightness: !lambda 'return id(${roomname}_lights).current_values.get_brightness() + 0.5;' - light.dim_relative: - id: living_room_lights + id: ${roomname}_lights relative_brightness: 5% - uart.write: id: uart0 data: Hello World - - uart.write: [0x00, 0x20, 0x30] - - uart.write: !lambda |- - return {}; + - uart.write: + id: uart0 + data: [0x00, 0x20, 0x30] + - uart.write: + id: uart0 + data: !lambda |- + return {}; i2c: sda: 21 @@ -143,6 +180,7 @@ uart: data_bits: 8 stop_bits: 1 rx_buffer_size: 512 + invert: false - id: adalight_uart tx_pin: GPIO25 @@ -237,6 +275,14 @@ sensor: window_size: 5 send_every: 5 send_first_at: 3 + - min: + window_size: 5 + send_every: 5 + send_first_at: 3 + - max: + window_size: 5 + send_every: 5 + send_first_at: 3 - sliding_window_moving_average: window_size: 15 send_every: 15 @@ -256,9 +302,9 @@ sensor: then: - lambda: |- ESP_LOGD("main", "Got value %f", x); - id(my_sensor).publish_state(42.0); - ESP_LOGI("main", "Value of my sensor: %f", id(my_sensor).state); - ESP_LOGI("main", "Raw Value of my sensor: %f", id(my_sensor).state); + id(${sensorname}_sensor).publish_state(42.0); + ESP_LOGI("main", "Value of my sensor: %f", id(${sensorname}_sensor).state); + ESP_LOGI("main", "Raw Value of my sensor: %f", id(${sensorname}_sensor).state); on_value_range: above: 5 below: 10 @@ -285,7 +331,7 @@ sensor: - platform: ads1115 multiplexer: 'A0_A1' gain: 1.024 - id: my_sensor + id: ${sensorname}_sensor filters: state_topic: hi/me retain: false @@ -435,7 +481,7 @@ sensor: name: 'HLW8012 Power' id: hlw8012_power energy: - name: "HLW8012 Energy" + name: 'HLW8012 Energy' id: hlw8012_energy update_interval: 15s current_resistor: 0.001 ohm @@ -549,6 +595,7 @@ sensor: reference_resistance: '430 Ω' rtd_nominal_resistance: '100 Ω' - platform: mhz19 + uart_id: uart0 co2: name: 'MH-Z19 CO2 Value' temperature: @@ -586,6 +633,13 @@ sensor: falling_edge: DECREMENT internal_filter: 13us update_interval: 15s + - platform: pulse_meter + name: 'Pulse Meter' + pin: GPIO12 + internal_filter: 100ms + timeout: 2 min + total: + name: 'Pulse Meter Total' - platform: rotary_encoder name: 'Rotary Encoder' id: rotary_encoder1 @@ -613,10 +667,23 @@ sensor: - platform: pulse_width name: Pulse Width pin: GPIO12 - - platform: senseair + - platform: sm300d2 + uart_id: uart0 co2: - name: 'SenseAir CO2 Value' - update_interval: 15s + name: 'SM300D2 CO2 Value' + formaldehyde: + name: 'SM300D2 Formaldehyde Value' + tvoc: + name: 'SM300D2 TVOC Value' + pm_2_5: + name: 'SM300D2 PM2.5 Value' + pm_10_0: + name: 'SM300D2 PM10 Value' + temperature: + name: 'SM300D2 Temperature Value' + humidity: + name: 'SM300D2 Humidity Value' + update_interval: 60s - platform: sht3xd temperature: name: 'Living Room Temperature 8' @@ -735,6 +802,7 @@ sensor: root["key"] = id(the_sensor).state; root["greeting"] = "Hello World"; - platform: sds011 + uart_id: uart0 pm_2_5: name: 'SDS011 PM2.5' pm_10_0: @@ -784,6 +852,7 @@ sensor: name: 'AQI' calculation_type: 'CAQI' - platform: teleinfo + uart_id: uart0 tags: - tag_name: 'HCHC' sensor: @@ -829,7 +898,7 @@ binary_sensor: - platform: gpio name: 'MCP23S08 Pin #1' pin: - mcp23s08: mcp23s08_hub + mcp23xxx: mcp23s08_hub # Use pin number 1 number: 1 # One of INPUT or INPUT_PULLUP @@ -838,12 +907,22 @@ binary_sensor: - platform: gpio name: 'MCP23S17 Pin #1' pin: - mcp23s17: mcp23s17_hub + mcp23xxx: mcp23s17_hub + # Use pin number 1 + number: 1 + # One of INPUT or INPUT_PULLUP + mode: INPUT_PULLUP + inverted: False + - platform: gpio + name: 'MCP23S17 Pin #1 with interrupt' + pin: + mcp23xxx: mcp23s17_hub # Use pin number 1 number: 1 # One of INPUT or INPUT_PULLUP mode: INPUT_PULLUP inverted: False + interrupt: FALLING - platform: gpio pin: GPIO9 name: 'Living Room Window' @@ -924,12 +1003,12 @@ binary_sensor: name: 'Garage Door Open' id: garage_door lambda: |- - if (isnan(id(my_sensor).state)) { + if (isnan(id(${sensorname}_sensor).state)) { // isnan checks if the ultrasonic sensor echo // has timed out, resulting in a NaN (not a number) state // in that case, return {} to indicate that we don't know. return {}; - } else if (id(my_sensor).state > 30) { + } else if (id(${sensorname}_sensor).state > 30) { // Garage Door is open. return true; } else { @@ -947,6 +1026,7 @@ binary_sensor: id: gpio_19 frequency: !lambda 'return 500.0;' - platform: pn532 + pn532_id: pn532_bs uid: 74-10-37-94 name: 'PN532 NFC Tag' - platform: rdm6300 @@ -962,14 +1042,14 @@ binary_sensor: - platform: gpio name: 'MCP21 binary sensor' pin: - mcp23017: mcp23017_hub + mcp23xxx: mcp23017_hub number: 1 mode: INPUT inverted: True - platform: gpio name: 'MCP22 binary sensor' pin: - mcp23008: mcp23008_hub + mcp23xxx: mcp23008_hub number: 7 mode: INPUT_PULLUP inverted: False @@ -1126,14 +1206,14 @@ output: - platform: gpio id: id22 pin: - mcp23017: mcp23017_hub + mcp23xxx: mcp23017_hub number: 0 mode: OUTPUT inverted: False - platform: gpio id: id23 pin: - mcp23008: mcp23008_hub + mcp23xxx: mcp23008_hub number: 0 mode: OUTPUT inverted: False @@ -1173,6 +1253,8 @@ output: - platform: esp32_dac pin: GPIO25 id: dac_output + - platform: mcp4725 + id: mcp4725_dac_output e131: @@ -1220,7 +1302,7 @@ light: state = 0; - platform: rgb name: 'Living Room Lights' - id: living_room_lights + id: ${roomname}_lights red: pca_0 green: pca_1 blue: pca_2 @@ -1376,14 +1458,14 @@ climate: name: TCL112 Climate With Sensor supports_heat: True supports_cool: True - sensor: my_sensor + sensor: ${sensorname}_sensor - platform: tcl112 name: TCL112 Climate - platform: coolix name: Coolix Climate With Sensor supports_heat: True supports_cool: True - sensor: my_sensor + sensor: ${sensorname}_sensor - platform: coolix name: Coolix Climate - platform: fujitsu_general @@ -1402,12 +1484,29 @@ climate: name: Toshiba Climate - platform: hitachi_ac344 name: Hitachi Climate + - platform: midea_ac + visual: + min_temperature: 18 °C + max_temperature: 25 °C + temperature_step: 0.1 °C + name: "Electrolux EACS" + beeper: true + outdoor_temperature: + name: "Temp" + power_usage: + name: "Power" + humidity_setpoint: + name: "Hum" + +midea_dongle: + uart_id: uart0 + strength_icon: true switch: - platform: gpio name: 'MCP23S08 Pin #0' pin: - mcp23s08: mcp23s08_hub + mcp23xxx: mcp23s08_hub # Use pin number 0 number: 0 mode: OUTPUT @@ -1415,7 +1514,7 @@ switch: - platform: gpio name: 'MCP23S17 Pin #0' pin: - mcp23s17: mcp23s17_hub + mcp23xxx: mcp23s17_hub # Use pin number 0 number: 1 mode: OUTPUT @@ -1451,6 +1550,12 @@ switch: turn_on_action: remote_transmitter.transmit_samsung: data: 0xABCDEF + - platform: template + name: Samsung36 + turn_on_action: + remote_transmitter.transmit_samsung36: + address: 0x0400 + command: 0x000E00FF - platform: template name: Sony turn_on_action: @@ -1542,6 +1647,9 @@ switch: - output.set_level: id: dac_output level: !lambda 'return 0.5;' + - output.set_level: + id: mcp4725_dac_output + level: !lambda 'return 0.5;' turn_off_action: - switch.turn_on: living_room_lights_off restore_state: False @@ -1582,11 +1690,18 @@ switch: id: my_switch state: !lambda 'return false;' - platform: uart + uart_id: uart0 name: 'UART String Output' data: 'DataToSend' - platform: uart + uart_id: uart0 name: 'UART Bytes Output' data: [0xDE, 0xAD, 0xBE, 0xEF] + - platform: uart + uart_id: uart0 + name: 'UART Recurring Output' + data: [0xDE, 0xAD, 0xBE, 0xEF] + send_every: 1s - platform: template assumed_state: yes name: Stepper Switch @@ -1620,13 +1735,10 @@ fan: direction_output: gpio_26 - platform: speed output: pca_6 + speed_count: 10 name: 'Living Room Fan 2' oscillation_output: gpio_19 direction_output: gpio_26 - speed: - low: 0.45 - medium: 0.75 - high: 1.0 oscillation_state_topic: oscillation/state/topic oscillation_command_topic: oscillation/command/topic speed_state_topic: speed/state/topic @@ -1653,7 +1765,7 @@ interval: color: - id: kbx_red red: 100% - green: 1% + green_int: 123 blue: 2% - id: kbx_blue red: 0% @@ -1690,15 +1802,16 @@ display: it.print("1234"); - platform: tm1637 clk_pin: - mcp23017: mcp23017_hub + mcp23xxx: mcp23017_hub number: 1 dio_pin: - mcp23017: mcp23017_hub + mcp23xxx: mcp23017_hub number: 2 intensity: 3 lambda: |- it.print("1234"); - platform: nextion + uart_id: uart0 lambda: |- it.set_component_value("gauge", 50); it.set_component_text("textview", "Hello World!"); @@ -1730,7 +1843,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1322_spi - model: "SSD1322 256x64" + model: 'SSD1322 256x64' cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 @@ -1776,24 +1889,6 @@ display: reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: waveshare_epaper - cs_pin: GPIO23 - dc_pin: GPIO23 - busy_pin: GPIO23 - reset_pin: GPIO23 - model: 2.90in - full_update_every: 30 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: waveshare_epaper - cs_pin: GPIO23 - dc_pin: GPIO23 - busy_pin: GPIO23 - reset_pin: GPIO23 - model: 2.90inv2 - full_update_every: 30 - lambda: |- - it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7789v cs_pin: GPIO5 dc_pin: GPIO16 @@ -1807,10 +1902,10 @@ display: dc_pin: GPIO16 reset_pin: GPIO23 rotation: 0 - devicewidth: 128 - deviceheight: 160 - colstart: 0 - rowstart: 0 + device_width: 128 + device_height: 160 + col_start: 0 + row_start: 0 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); tm1651: @@ -1826,6 +1921,7 @@ status_led: pin: GPIO2 pn532_spi: + id: pn532_bs cs_pin: GPIO23 update_interval: 1s on_tag: @@ -1838,6 +1934,7 @@ pn532_spi: pn532_i2c: rdm6300: + uart_id: uart0 rc522_spi: cs_pin: GPIO23 @@ -1853,6 +1950,7 @@ rc522_i2c: ESP_LOGD("main", "Found tag %s", x.c_str()); gps: + uart_id: uart0 time: - platform: sntp @@ -1875,8 +1973,7 @@ time: update_interval: never on_time: seconds: 0 - then: - ds1307.read_time + then: ds1307.read_time cover: - platform: template @@ -1950,10 +2047,10 @@ text_sensor: qos: 2 on_value: - text_sensor.template.publish: - id: template_text + id: ${textname}_text state: Hello World - text_sensor.template.publish: - id: template_text + id: ${textname}_text state: |- return "Hello World2"; - globals.set: @@ -1964,7 +2061,7 @@ text_sensor: data: [0x10, 0x20, 0x30] - platform: template name: Template Text Sensor - id: template_text + id: ${textname}_text - platform: wifi_info ip_address: name: 'IP Address' @@ -2006,4 +2103,4 @@ canbus: condition: lambda: 'return x[0] == 0x11;' then: - light.toggle: living_room_lights + light.toggle: ${roomname}_lights diff --git a/tests/test2.yaml b/tests/test2.yaml index b975090531f3..616fe32139d6 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -45,6 +45,12 @@ ota: logger: level: DEBUG +deep_sleep: + run_duration: 20s + sleep_duration: 50s + wakeup_pin: GPIO39 + wakeup_pin_mode: INVERT_WAKEUP + as3935_i2c: irq_pin: GPIO12 @@ -64,6 +70,18 @@ sensor: - platform: ble_rssi service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' name: 'BLE Test Service 128' + - platform: senseair + id: senseair0 + co2: + name: 'SenseAir CO2 Value' + on_value: + then: + - senseair.background_calibration: senseair0 + - senseair.background_calibration_result: senseair0 + - senseair.abc_get_period: senseair0 + - senseair.abc_enable: senseair0 + - senseair.abc_disable: senseair0 + update_interval: 15s - platform: ruuvitag mac_address: FF:56:D3:2F:7D:E8 humidity: @@ -181,6 +199,14 @@ sensor: name: 'ATC Battery-Level' battery_voltage: name: 'ATC Battery-Voltage' + - platform: inkbird_ibsth1_mini + mac_address: 38:81:D7:0A:9C:11 + temperature: + name: 'Inkbird IBS-TH1 Temperature' + humidity: + name: 'Inkbird IBS-TH1 Humidity' + battery_level: + name: 'Inkbird IBS-TH1 Battery Level' time: - platform: homeassistant @@ -294,6 +320,8 @@ text_sensor: - homeassistant.tag_scanned: tag: 1234-abcd - homeassistant.tag_scanned: 1234-abcd + - deep_sleep.enter: + sleep_duration: 30min - platform: template name: 'Template Text Sensor' lambda: |- diff --git a/tests/test3.yaml b/tests/test3.yaml index 64d5dbfc9bde..ee95e9d35fa8 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -197,10 +197,6 @@ uart: rx_pin: GPIO3 baud_rate: 115200 - - id: adalight_uart - rx_pin: GPIO3 - baud_rate: 115200 - ota: safe_mode: True port: 3286 @@ -229,6 +225,8 @@ sensor: name: 'VL53L0x Distance' address: 0x29 update_interval: 60s + enable_pin: GPIO13 + timeout: 200us - platform: apds9960 type: clear name: APDS9960 Clear @@ -558,14 +556,14 @@ switch: - platform: gpio id: gpio_switch1 pin: - mcp23017: mcp23017_hub + mcp23xxx: mcp23017_hub number: 0 mode: OUTPUT interlock: &interlock [gpio_switch1, gpio_switch2, gpio_switch3] - platform: gpio id: gpio_switch2 pin: - mcp23008: mcp23008_hub + mcp23xxx: mcp23008_hub number: 0 mode: OUTPUT interlock: *interlock @@ -812,7 +810,6 @@ light: effects: - wled: - adalight: - uart_id: adalight_uart - e131: universe: 1 - platform: hbridge @@ -842,6 +839,8 @@ sim800l: - sim800l.send_sms: message: 'hello you' recipient: '+1234' + - sim800l.dial: + recipient: '+1234' dfplayer: on_finished_playback: @@ -886,6 +885,25 @@ rf_bridge: code: 'ABC123' - rf_bridge.send_raw: raw: 'AAA5070008001000ABC12355' + - http_request.get: + url: https://esphome.io + headers: + Content-Type: application/json + verify_ssl: false + - http_request.post: + url: https://esphome.io + verify_ssl: false + json: + key: !lambda |- + return id(version_sensor).state; + greeting: 'Hello World' + - http_request.send: + method: PUT + url: https://esphome.io + headers: + Content-Type: application/json + body: 'Some data' + verify_ssl: false display: - platform: max7219digit @@ -897,3 +915,7 @@ display: id: my_matrix lambda: |- it.printdigit("hello"); + +http_request: + useragent: esphome/device + timeout: 10s diff --git a/tests/test4.yaml b/tests/test4.yaml index bfeff01e9399..ed63a1ac143a 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -99,3 +99,64 @@ switch: - platform: tuya id: tuya_switch switch_datapoint: 1 + +light: + - platform: fastled_clockless + id: led_matrix_32x8 + name: "led_matrix_32x8" + chipset: WS2812B + pin: GPIO15 + num_leds: 256 + rgb_order: GRB + default_transition_length: 0s + color_correct: [50%, 50%, 50%] + +display: + - platform: addressable_light + id: led_matrix_32x8_display + addressable_light_id: led_matrix_32x8 + width: 32 + height: 8 + pixel_mapper: |- + if (x % 2 == 0) { + return (x * 8) + y; + } + return (x * 8) + (7 - y); + lambda: |- + Color red = Color(0xFF0000); + Color green = Color(0x00FF00); + Color blue = Color(0x0000FF); + it.rectangle(0, 0, it.get_width(), it.get_height(), red); + it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green); + it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue); + it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); + rotation: 0° + update_interval: 16ms + - platform: waveshare_epaper + cs_pin: GPIO23 + dc_pin: GPIO23 + busy_pin: GPIO23 + reset_pin: GPIO23 + model: 2.13in-ttgo-b1 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: GPIO23 + dc_pin: GPIO23 + busy_pin: GPIO23 + reset_pin: GPIO23 + model: 2.90in + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: GPIO23 + dc_pin: GPIO23 + busy_pin: GPIO23 + reset_pin: GPIO23 + model: 2.90inv2 + full_update_every: 30 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index adef39a0b371..41d0f3dadb82 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -27,4 +27,3 @@ def fixture_path() -> Path: Location of all fixture files. """ return here / "fixtures" - diff --git a/tests/unit_tests/strategies.py b/tests/unit_tests/strategies.py index f4763f047fa9..4bc0482f5f1f 100644 --- a/tests/unit_tests/strategies.py +++ b/tests/unit_tests/strategies.py @@ -12,4 +12,6 @@ def mac_addr_strings(): This consists of six strings representing integers [0..255], without zero-padding, joined by dots. """ - return st.builds("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}".format, *(6 * [st.integers(0, 255)])) + return st.builds( + "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}".format, *(6 * [st.integers(0, 255)]) + ) diff --git a/tests/unit_tests/test_codegen.py b/tests/unit_tests/test_codegen.py index 931e191de69c..9f402465fad6 100644 --- a/tests/unit_tests/test_codegen.py +++ b/tests/unit_tests/test_codegen.py @@ -4,23 +4,75 @@ # Test interface remains the same. -@pytest.mark.parametrize("attr", ( - # from cpp_generator - "Expression", "RawExpression", "RawStatement", "TemplateArguments", - "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 cpp_helpers - "gpio_pin_expression", "register_component", "build_registry_entry", - "build_registry_list", "extract_registry_entry_config", "register_parented", - "global_ns", "void", "nullptr", "float_", "double", "bool_", "int_", "std_ns", "std_string", - "std_vector", "uint8", "uint16", "uint32", "int32", "const_char_ptr", "NAN", - "esphome_ns", "App", "Nameable", "Component", "ComponentPtr", - # from cpp_types - "PollingComponent", "Application", "optional", "arduino_json_ns", "JsonObject", - "JsonObjectRef", "JsonObjectConstRef", "Controller", "GPIOPin" -)) +@pytest.mark.parametrize( + "attr", + ( + # from cpp_generator + "Expression", + "RawExpression", + "RawStatement", + "TemplateArguments", + "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 cpp_helpers + "gpio_pin_expression", + "register_component", + "build_registry_entry", + "build_registry_list", + "extract_registry_entry_config", + "register_parented", + "global_ns", + "void", + "nullptr", + "float_", + "double", + "bool_", + "int_", + "std_ns", + "std_string", + "std_vector", + "uint8", + "uint16", + "uint32", + "int32", + "const_char_ptr", + "NAN", + "esphome_ns", + "App", + "Nameable", + "Component", + "ComponentPtr", + # from cpp_types + "PollingComponent", + "Application", + "optional", + "arduino_json_ns", + "JsonObject", + "JsonObjectRef", + "JsonObjectConstRef", + "Controller", + "GPIOPin", + ), +) def test_exists(attr): assert hasattr(cg, attr) diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 846df71a942c..949d4251eecc 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -2,7 +2,7 @@ import string from hypothesis import given, example -from hypothesis.strategies import one_of, text, integers, booleans, builds +from hypothesis.strategies import one_of, text, integers, builds from esphome import config_validation from esphome.config_validation import Invalid @@ -24,7 +24,7 @@ def test_alphanumeric__valid(value): @pytest.mark.parametrize("value", ("£23", "Foo!")) def test_alphanumeric__invalid(value): with pytest.raises(Invalid): - actual = config_validation.alphanumeric(value) + config_validation.alphanumeric(value) @given(value=text(alphabet=string.ascii_lowercase + string.digits + "_-")) @@ -34,9 +34,7 @@ def test_valid_name__valid(value): assert actual == value -@pytest.mark.parametrize("value", ( - "foo bar", "FooBar", "foo::bar" -)) +@pytest.mark.parametrize("value", ("foo bar", "FooBar", "foo::bar")) def test_valid_name__invalid(value): with pytest.raises(Invalid): config_validation.valid_name(value) @@ -49,9 +47,7 @@ def test_string__valid(value): assert actual == str(value) -@pytest.mark.parametrize("value", ( - {}, [], True, False, None -)) +@pytest.mark.parametrize("value", ({}, [], True, False, None)) def test_string__invalid(value): with pytest.raises(Invalid): config_validation.string(value) @@ -83,23 +79,17 @@ def test_icon__invalid(): config_validation.icon("foo") -@pytest.mark.parametrize("value", ( - "True", "YES", "on", "enAblE", True -)) +@pytest.mark.parametrize("value", ("True", "YES", "on", "enAblE", True)) def test_boolean__valid_true(value): assert config_validation.boolean(value) is True -@pytest.mark.parametrize("value", ( - "False", "NO", "off", "disAblE", False -)) +@pytest.mark.parametrize("value", ("False", "NO", "off", "disAblE", False)) def test_boolean__valid_false(value): assert config_validation.boolean(value) is False -@pytest.mark.parametrize("value", ( - None, 1, 0, "foo" -)) +@pytest.mark.parametrize("value", (None, 1, 0, "foo")) def test_boolean__invalid(value): with pytest.raises(Invalid, match="Expected boolean value"): config_validation.boolean(value) diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index 27b64ec3d5b9..fd3f171275e2 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -8,13 +8,16 @@ class TestHexInt: - @pytest.mark.parametrize("value, expected", ( - (1, "0x01"), - (255, "0xFF"), - (128, "0x80"), - (256, "0x100"), - (-1, "-0x01"), # TODO: this currently fails - )) + @pytest.mark.parametrize( + "value, expected", + ( + (1, "0x01"), + (255, "0xFF"), + (128, "0x80"), + (256, "0x100"), + (-1, "-0x01"), # TODO: this currently fails + ), + ) def test_str(self, value, expected): target = core.HexInt(value) @@ -68,18 +71,14 @@ def test_as_hex(self): assert actual.text == "0xDEADBEEF00FFULL" -@pytest.mark.parametrize("value", ( - 1, 2, -1, 0, 1.0, -1.0, 42.0009, -42.0009 -)) +@pytest.mark.parametrize("value", (1, 2, -1, 0, 1.0, -1.0, 42.0009, -42.0009)) def test_is_approximately_integer__in_range(value): actual = core.is_approximately_integer(value) assert actual is True -@pytest.mark.parametrize("value", ( - 42.01, -42.01, 1.5 -)) +@pytest.mark.parametrize("value", (42.01, -42.01, 1.5)) def test_is_approximately_integer__not_in_range(value): actual = core.is_approximately_integer(value) @@ -87,26 +86,29 @@ def test_is_approximately_integer__not_in_range(value): class TestTimePeriod: - @pytest.mark.parametrize("kwargs, expected", ( - ({}, {}), - ({"microseconds": 1}, {"microseconds": 1}), - ({"microseconds": 1.0001}, {"microseconds": 1}), - ({"milliseconds": 2}, {"milliseconds": 2}), - ({"milliseconds": 2.0001}, {"milliseconds": 2}), - ({"milliseconds": 2.01}, {"milliseconds": 2, "microseconds": 10}), - ({"seconds": 3}, {"seconds": 3}), - ({"seconds": 3.0001}, {"seconds": 3}), - ({"seconds": 3.01}, {"seconds": 3, "milliseconds": 10}), - ({"minutes": 4}, {"minutes": 4}), - ({"minutes": 4.0001}, {"minutes": 4}), - ({"minutes": 4.1}, {"minutes": 4, "seconds": 6}), - ({"hours": 5}, {"hours": 5}), - ({"hours": 5.0001}, {"hours": 5}), - ({"hours": 5.1}, {"hours": 5, "minutes": 6}), - ({"days": 6}, {"days": 6}), - ({"days": 6.0001}, {"days": 6}), - ({"days": 6.1}, {"days": 6, "hours": 2, "minutes": 24}), - )) + @pytest.mark.parametrize( + "kwargs, expected", + ( + ({}, {}), + ({"microseconds": 1}, {"microseconds": 1}), + ({"microseconds": 1.0001}, {"microseconds": 1}), + ({"milliseconds": 2}, {"milliseconds": 2}), + ({"milliseconds": 2.0001}, {"milliseconds": 2}), + ({"milliseconds": 2.01}, {"milliseconds": 2, "microseconds": 10}), + ({"seconds": 3}, {"seconds": 3}), + ({"seconds": 3.0001}, {"seconds": 3}), + ({"seconds": 3.01}, {"seconds": 3, "milliseconds": 10}), + ({"minutes": 4}, {"minutes": 4}), + ({"minutes": 4.0001}, {"minutes": 4}), + ({"minutes": 4.1}, {"minutes": 4, "seconds": 6}), + ({"hours": 5}, {"hours": 5}), + ({"hours": 5.0001}, {"hours": 5}), + ({"hours": 5.1}, {"hours": 5, "minutes": 6}), + ({"days": 6}, {"days": 6}), + ({"days": 6.0001}, {"days": 6}), + ({"days": 6.1}, {"days": 6, "hours": 2, "minutes": 24}), + ), + ) def test_init(self, kwargs, expected): target = core.TimePeriod(**kwargs) @@ -118,26 +120,29 @@ def test_init__microseconds_with_fraction(self): with pytest.raises(ValueError, match="Maximum precision is microseconds"): core.TimePeriod(microseconds=1.1) - @pytest.mark.parametrize("kwargs, expected", ( - ({}, "0s"), - ({"microseconds": 1}, "1us"), - ({"microseconds": 1.0001}, "1us"), - ({"milliseconds": 2}, "2ms"), - ({"milliseconds": 2.0001}, "2ms"), - ({"milliseconds": 2.01}, "2010us"), - ({"seconds": 3}, "3s"), - ({"seconds": 3.0001}, "3s"), - ({"seconds": 3.01}, "3010ms"), - ({"minutes": 4}, "4min"), - ({"minutes": 4.0001}, "4min"), - ({"minutes": 4.1}, "246s"), - ({"hours": 5}, "5h"), - ({"hours": 5.0001}, "5h"), - ({"hours": 5.1}, "306min"), - ({"days": 6}, "6d"), - ({"days": 6.0001}, "6d"), - ({"days": 6.1}, "8784min"), - )) + @pytest.mark.parametrize( + "kwargs, expected", + ( + ({}, "0s"), + ({"microseconds": 1}, "1us"), + ({"microseconds": 1.0001}, "1us"), + ({"milliseconds": 2}, "2ms"), + ({"milliseconds": 2.0001}, "2ms"), + ({"milliseconds": 2.01}, "2010us"), + ({"seconds": 3}, "3s"), + ({"seconds": 3.0001}, "3s"), + ({"seconds": 3.01}, "3010ms"), + ({"minutes": 4}, "4min"), + ({"minutes": 4.0001}, "4min"), + ({"minutes": 4.1}, "246s"), + ({"hours": 5}, "5h"), + ({"hours": 5.0001}, "5h"), + ({"hours": 5.1}, "306min"), + ({"days": 6}, "6d"), + ({"days": 6.0001}, "6d"), + ({"days": 6.1}, "8784min"), + ), + ) def test_str(self, kwargs, expected): target = core.TimePeriod(**kwargs) @@ -145,61 +150,59 @@ def test_str(self, kwargs, expected): assert actual == expected - @pytest.mark.parametrize("comparison, other, expected", ( - ("__eq__", core.TimePeriod(microseconds=900), False), - ("__eq__", core.TimePeriod(milliseconds=1), True), - ("__eq__", core.TimePeriod(microseconds=1100), False), - ("__eq__", 1000, NotImplemented), - ("__eq__", "1000", NotImplemented), - ("__eq__", True, NotImplemented), - ("__eq__", object(), NotImplemented), - ("__eq__", None, NotImplemented), - - ("__ne__", core.TimePeriod(microseconds=900), True), - ("__ne__", core.TimePeriod(milliseconds=1), False), - ("__ne__", core.TimePeriod(microseconds=1100), True), - ("__ne__", 1000, NotImplemented), - ("__ne__", "1000", NotImplemented), - ("__ne__", True, NotImplemented), - ("__ne__", object(), NotImplemented), - ("__ne__", None, NotImplemented), - - ("__lt__", core.TimePeriod(microseconds=900), False), - ("__lt__", core.TimePeriod(milliseconds=1), False), - ("__lt__", core.TimePeriod(microseconds=1100), True), - ("__lt__", 1000, NotImplemented), - ("__lt__", "1000", NotImplemented), - ("__lt__", True, NotImplemented), - ("__lt__", object(), NotImplemented), - ("__lt__", None, NotImplemented), - - ("__gt__", core.TimePeriod(microseconds=900), True), - ("__gt__", core.TimePeriod(milliseconds=1), False), - ("__gt__", core.TimePeriod(microseconds=1100), False), - ("__gt__", 1000, NotImplemented), - ("__gt__", "1000", NotImplemented), - ("__gt__", True, NotImplemented), - ("__gt__", object(), NotImplemented), - ("__gt__", None, NotImplemented), - - ("__le__", core.TimePeriod(microseconds=900), False), - ("__le__", core.TimePeriod(milliseconds=1), True), - ("__le__", core.TimePeriod(microseconds=1100), True), - ("__le__", 1000, NotImplemented), - ("__le__", "1000", NotImplemented), - ("__le__", True, NotImplemented), - ("__le__", object(), NotImplemented), - ("__le__", None, NotImplemented), - - ("__ge__", core.TimePeriod(microseconds=900), True), - ("__ge__", core.TimePeriod(milliseconds=1), True), - ("__ge__", core.TimePeriod(microseconds=1100), False), - ("__ge__", 1000, NotImplemented), - ("__ge__", "1000", NotImplemented), - ("__ge__", True, NotImplemented), - ("__ge__", object(), NotImplemented), - ("__ge__", None, NotImplemented), - )) + @pytest.mark.parametrize( + "comparison, other, expected", + ( + ("__eq__", core.TimePeriod(microseconds=900), False), + ("__eq__", core.TimePeriod(milliseconds=1), True), + ("__eq__", core.TimePeriod(microseconds=1100), False), + ("__eq__", 1000, NotImplemented), + ("__eq__", "1000", NotImplemented), + ("__eq__", True, NotImplemented), + ("__eq__", object(), NotImplemented), + ("__eq__", None, NotImplemented), + ("__ne__", core.TimePeriod(microseconds=900), True), + ("__ne__", core.TimePeriod(milliseconds=1), False), + ("__ne__", core.TimePeriod(microseconds=1100), True), + ("__ne__", 1000, NotImplemented), + ("__ne__", "1000", NotImplemented), + ("__ne__", True, NotImplemented), + ("__ne__", object(), NotImplemented), + ("__ne__", None, NotImplemented), + ("__lt__", core.TimePeriod(microseconds=900), False), + ("__lt__", core.TimePeriod(milliseconds=1), False), + ("__lt__", core.TimePeriod(microseconds=1100), True), + ("__lt__", 1000, NotImplemented), + ("__lt__", "1000", NotImplemented), + ("__lt__", True, NotImplemented), + ("__lt__", object(), NotImplemented), + ("__lt__", None, NotImplemented), + ("__gt__", core.TimePeriod(microseconds=900), True), + ("__gt__", core.TimePeriod(milliseconds=1), False), + ("__gt__", core.TimePeriod(microseconds=1100), False), + ("__gt__", 1000, NotImplemented), + ("__gt__", "1000", NotImplemented), + ("__gt__", True, NotImplemented), + ("__gt__", object(), NotImplemented), + ("__gt__", None, NotImplemented), + ("__le__", core.TimePeriod(microseconds=900), False), + ("__le__", core.TimePeriod(milliseconds=1), True), + ("__le__", core.TimePeriod(microseconds=1100), True), + ("__le__", 1000, NotImplemented), + ("__le__", "1000", NotImplemented), + ("__le__", True, NotImplemented), + ("__le__", object(), NotImplemented), + ("__le__", None, NotImplemented), + ("__ge__", core.TimePeriod(microseconds=900), True), + ("__ge__", core.TimePeriod(milliseconds=1), True), + ("__ge__", core.TimePeriod(microseconds=1100), False), + ("__ge__", 1000, NotImplemented), + ("__ge__", "1000", NotImplemented), + ("__ge__", True, NotImplemented), + ("__ge__", object(), NotImplemented), + ("__ge__", None, NotImplemented), + ), + ) def test_comparison(self, comparison, other, expected): target = core.TimePeriod(microseconds=1000) @@ -238,19 +241,19 @@ def test_parts(self): "it.strftime(64, 0, ", "my_font", "", - ", TextAlign::TOP_CENTER, \"%H:%M:%S\", ", + ', TextAlign::TOP_CENTER, "%H:%M:%S", ', "esptime", ".", "now());\nit.printf(64, 16, ", "my_font2", "", - ", TextAlign::TOP_CENTER, \"%.1f°C (%.1f%%)\", ", + ', TextAlign::TOP_CENTER, "%.1f°C (%.1f%%)", ', "office_tmp", ".", "state, ", "office_hmd", ".", - "state);\n \nint x = 4; " + "state);\n \nint x = 4; ", ] def test_requires_ids(self): @@ -296,24 +299,33 @@ class TestID: def target(self): return core.ID(None, is_declaration=True, type="binary_sensor::Example") - @pytest.mark.parametrize("id, is_manual, expected", ( - ("foo", None, True), - (None, None, False), - ("foo", True, True), - ("foo", False, False), - (None, True, True), - )) + @pytest.mark.parametrize( + "id, is_manual, expected", + ( + ("foo", None, True), + (None, None, False), + ("foo", True, True), + ("foo", False, False), + (None, True, True), + ), + ) def test_init__resolve_is_manual(self, id, is_manual, expected): target = core.ID(id, is_manual=is_manual) assert target.is_manual == expected - @pytest.mark.parametrize("registered_ids, expected", ( - ([], "binary_sensor_example"), - (["binary_sensor_example"], "binary_sensor_example_2"), - (["foo"], "binary_sensor_example"), - (["binary_sensor_example", "foo", "binary_sensor_example_2"], "binary_sensor_example_3"), - )) + @pytest.mark.parametrize( + "registered_ids, expected", + ( + ([], "binary_sensor_example"), + (["binary_sensor_example"], "binary_sensor_example_2"), + (["foo"], "binary_sensor_example"), + ( + ["binary_sensor_example", "foo", "binary_sensor_example_2"], + "binary_sensor_example_3", + ), + ), + ) def test_resolve(self, target, registered_ids, expected): actual = target.resolve(registered_ids) @@ -326,18 +338,23 @@ def test_copy(self, target): actual = target.copy() assert actual is not target - assert all(getattr(actual, n) == getattr(target, n) - for n in ("id", "is_declaration", "type", "is_manual")) - - @pytest.mark.parametrize("comparison, other, expected", ( - ("__eq__", core.ID(id="foo"), True), - ("__eq__", core.ID(id="bar"), False), - ("__eq__", 1000, NotImplemented), - ("__eq__", "1000", NotImplemented), - ("__eq__", True, NotImplemented), - ("__eq__", object(), NotImplemented), - ("__eq__", None, NotImplemented), - )) + assert all( + getattr(actual, n) == getattr(target, n) + for n in ("id", "is_declaration", "type", "is_manual") + ) + + @pytest.mark.parametrize( + "comparison, other, expected", + ( + ("__eq__", core.ID(id="foo"), True), + ("__eq__", core.ID(id="bar"), False), + ("__eq__", 1000, NotImplemented), + ("__eq__", "1000", NotImplemented), + ("__eq__", True, NotImplemented), + ("__eq__", object(), NotImplemented), + ("__eq__", None, NotImplemented), + ), + ) def test_comparison(self, comparison, other, expected): target = core.ID(id="foo") @@ -384,14 +401,17 @@ def test_str(self, target): class TestDefine: - @pytest.mark.parametrize("name, value, prop, expected", ( - ("ANSWER", None, "as_build_flag", "-DANSWER"), - ("ANSWER", None, "as_macro", "#define ANSWER"), - ("ANSWER", None, "as_tuple", ("ANSWER", None)), - ("ANSWER", 42, "as_build_flag", "-DANSWER=42"), - ("ANSWER", 42, "as_macro", "#define ANSWER 42"), - ("ANSWER", 42, "as_tuple", ("ANSWER", 42)), - )) + @pytest.mark.parametrize( + "name, value, prop, expected", + ( + ("ANSWER", None, "as_build_flag", "-DANSWER"), + ("ANSWER", None, "as_macro", "#define ANSWER"), + ("ANSWER", None, "as_tuple", ("ANSWER", None)), + ("ANSWER", 42, "as_build_flag", "-DANSWER=42"), + ("ANSWER", 42, "as_macro", "#define ANSWER 42"), + ("ANSWER", 42, "as_tuple", ("ANSWER", 42)), + ), + ) def test_properties(self, name, value, prop, expected): target = core.Define(name, value) @@ -399,18 +419,21 @@ def test_properties(self, name, value, prop, expected): assert actual == expected - @pytest.mark.parametrize("comparison, other, expected", ( - ("__eq__", core.Define(name="FOO", value=42), True), - ("__eq__", core.Define(name="FOO", value=13), False), - ("__eq__", core.Define(name="FOO"), False), - ("__eq__", core.Define(name="BAR", value=42), False), - ("__eq__", core.Define(name="BAR"), False), - ("__eq__", 1000, NotImplemented), - ("__eq__", "1000", NotImplemented), - ("__eq__", True, NotImplemented), - ("__eq__", object(), NotImplemented), - ("__eq__", None, NotImplemented), - )) + @pytest.mark.parametrize( + "comparison, other, expected", + ( + ("__eq__", core.Define(name="FOO", value=42), True), + ("__eq__", core.Define(name="FOO", value=13), False), + ("__eq__", core.Define(name="FOO"), False), + ("__eq__", core.Define(name="BAR", value=42), False), + ("__eq__", core.Define(name="BAR"), False), + ("__eq__", 1000, NotImplemented), + ("__eq__", "1000", NotImplemented), + ("__eq__", True, NotImplemented), + ("__eq__", object(), NotImplemented), + ("__eq__", None, NotImplemented), + ), + ) def test_comparison(self, comparison, other, expected): target = core.Define(name="FOO", value=42) @@ -420,12 +443,15 @@ def test_comparison(self, comparison, other, expected): class TestLibrary: - @pytest.mark.parametrize("name, value, prop, expected", ( - ("mylib", None, "as_lib_dep", "mylib"), - ("mylib", None, "as_tuple", ("mylib", None)), - ("mylib", "1.2.3", "as_lib_dep", "mylib@1.2.3"), - ("mylib", "1.2.3", "as_tuple", ("mylib", "1.2.3")), - )) + @pytest.mark.parametrize( + "name, value, prop, expected", + ( + ("mylib", None, "as_lib_dep", "mylib"), + ("mylib", None, "as_tuple", ("mylib", None)), + ("mylib", "1.2.3", "as_lib_dep", "mylib@1.2.3"), + ("mylib", "1.2.3", "as_tuple", ("mylib", "1.2.3")), + ), + ) def test_properties(self, name, value, prop, expected): target = core.Library(name, value) @@ -433,16 +459,19 @@ def test_properties(self, name, value, prop, expected): assert actual == expected - @pytest.mark.parametrize("comparison, other, expected", ( - ("__eq__", core.Library(name="libfoo", version="1.2.3"), True), - ("__eq__", core.Library(name="libfoo", version="1.2.4"), False), - ("__eq__", core.Library(name="libbar", version="1.2.3"), False), - ("__eq__", 1000, NotImplemented), - ("__eq__", "1000", NotImplemented), - ("__eq__", True, NotImplemented), - ("__eq__", object(), NotImplemented), - ("__eq__", None, NotImplemented), - )) + @pytest.mark.parametrize( + "comparison, other, expected", + ( + ("__eq__", core.Library(name="libfoo", version="1.2.3"), True), + ("__eq__", core.Library(name="libfoo", version="1.2.4"), False), + ("__eq__", core.Library(name="libbar", version="1.2.3"), False), + ("__eq__", 1000, NotImplemented), + ("__eq__", "1000", NotImplemented), + ("__eq__", True, NotImplemented), + ("__eq__", object(), NotImplemented), + ("__eq__", None, NotImplemented), + ), + ) def test_comparison(self, comparison, other, expected): target = core.Library(name="libfoo", version="1.2.3") diff --git a/tests/unit_tests/test_cpp_generator.py b/tests/unit_tests/test_cpp_generator.py index b130124b5472..5a8087ffa98c 100644 --- a/tests/unit_tests/test_cpp_generator.py +++ b/tests/unit_tests/test_cpp_generator.py @@ -9,18 +9,18 @@ class TestExpressions: - @pytest.mark.parametrize("target, expected", ( - (cg.RawExpression("foo && bar"), "foo && bar"), - - (cg.AssignmentExpression(None, None, "foo", "bar", None), 'foo = "bar"'), - (cg.AssignmentExpression(ct.float_, "*", "foo", 1, None), 'float *foo = 1'), - (cg.AssignmentExpression(ct.float_, "", "foo", 1, None), 'float foo = 1'), - - (cg.VariableDeclarationExpression(ct.int32, "*", "foo"), "int32_t *foo"), - (cg.VariableDeclarationExpression(ct.int32, "", "foo"), "int32_t foo"), - - (cg.ParameterExpression(ct.std_string, "foo"), "std::string foo"), - )) + @pytest.mark.parametrize( + "target, expected", + ( + (cg.RawExpression("foo && bar"), "foo && bar"), + (cg.AssignmentExpression(None, None, "foo", "bar", None), 'foo = "bar"'), + (cg.AssignmentExpression(ct.float_, "*", "foo", 1, None), "float *foo = 1"), + (cg.AssignmentExpression(ct.float_, "", "foo", 1, None), "float foo = 1"), + (cg.VariableDeclarationExpression(ct.int32, "*", "foo"), "int32_t *foo"), + (cg.VariableDeclarationExpression(ct.int32, "", "foo"), "int32_t foo"), + (cg.ParameterExpression(ct.std_string, "foo"), "std::string foo"), + ), + ) def test_str__simple(self, target: cg.Expression, expected: str): actual = str(target) @@ -67,10 +67,7 @@ def test_iter(self): class TestCallExpression: def test_str__no_template_args(self): - target = cg.CallExpression( - cg.RawExpression("my_function"), - 1, "2", False - ) + target = cg.CallExpression(cg.RawExpression("my_function"), 1, "2", False) actual = str(target) @@ -80,7 +77,9 @@ def test_str__with_template_args(self): target = cg.CallExpression( cg.RawExpression("my_function"), cg.TemplateArguments(int, float), - 1, "2", False + 1, + "2", + False, ) actual = str(target) @@ -100,36 +99,32 @@ def test_str(self): actual = str(target) - assert actual == 'foo::MyStruct{\n' \ - ' .state = "on",\n' \ - ' .min_length = 1,\n' \ - ' .max_length = 5,\n' \ - '}' + assert ( + actual == "foo::MyStruct{\n" + ' .state = "on",\n' + " .min_length = 1,\n" + " .max_length = 5,\n" + "}" + ) class TestArrayInitializer: def test_str__empty(self): - target = cg.ArrayInitializer( - None, None - ) + target = cg.ArrayInitializer(None, None) actual = str(target) assert actual == "{}" def test_str__not_multiline(self): - target = cg.ArrayInitializer( - 1, 2, 3, 4 - ) + target = cg.ArrayInitializer(1, 2, 3, 4) actual = str(target) assert actual == "{1, 2, 3, 4}" def test_str__multiline(self): - target = cg.ArrayInitializer( - 1, 2, 3, 4, multiline=True - ) + target = cg.ArrayInitializer(1, 2, 3, 4, multiline=True) actual = str(target) @@ -169,7 +164,7 @@ def test_str__no_return(self): def test_str__with_return(self): target = cg.LambdaExpression( - ("return (foo == 5) && (bar < 10));", ), + ("return (foo == 5) && (bar < 10));",), cg.ParameterListExpression((int, "foo"), (float, "bar")), "=", bool, @@ -185,27 +180,26 @@ def test_str__with_return(self): class TestLiterals: - @pytest.mark.parametrize("target, expected", ( - (cg.StringLiteral("foo"), '"foo"'), - - (cg.IntLiteral(0), "0"), - (cg.IntLiteral(42), "42"), - (cg.IntLiteral(4304967295), "4304967295ULL"), - (cg.IntLiteral(2150483647), "2150483647UL"), - (cg.IntLiteral(-2150083647), "-2150083647LL"), - - (cg.BoolLiteral(True), "true"), - (cg.BoolLiteral(False), "false"), - - (cg.HexIntLiteral(0), "0x00"), - (cg.HexIntLiteral(42), "0x2A"), - (cg.HexIntLiteral(682), "0x2AA"), - - (cg.FloatLiteral(0.0), "0.0f"), - (cg.FloatLiteral(4.2), "4.2f"), - (cg.FloatLiteral(1.23456789), "1.23456789f"), - (cg.FloatLiteral(math.nan), "NAN"), - )) + @pytest.mark.parametrize( + "target, expected", + ( + (cg.StringLiteral("foo"), '"foo"'), + (cg.IntLiteral(0), "0"), + (cg.IntLiteral(42), "42"), + (cg.IntLiteral(4304967295), "4304967295ULL"), + (cg.IntLiteral(2150483647), "2150483647UL"), + (cg.IntLiteral(-2150083647), "-2150083647LL"), + (cg.BoolLiteral(True), "true"), + (cg.BoolLiteral(False), "false"), + (cg.HexIntLiteral(0), "0x00"), + (cg.HexIntLiteral(42), "0x2A"), + (cg.HexIntLiteral(682), "0x2AA"), + (cg.FloatLiteral(0.0), "0.0f"), + (cg.FloatLiteral(4.2), "4.2f"), + (cg.FloatLiteral(1.23456789), "1.23456789f"), + (cg.FloatLiteral(math.nan), "NAN"), + ), + ) def test_str__simple(self, target: cg.Literal, expected: str): actual = str(target) @@ -216,7 +210,9 @@ def test_str__simple(self, target: cg.Literal, expected: str): FAKE_ENUM_VALUE.enum_value = "foo" -@pytest.mark.parametrize("obj, expected_type", ( +@pytest.mark.parametrize( + "obj, expected_type", + ( (cg.RawExpression("foo"), cg.RawExpression), (FAKE_ENUM_VALUE, cg.StringLiteral), (True, cg.BoolLiteral), @@ -230,49 +226,59 @@ def test_str__simple(self, target: cg.Literal, expected: str): (cg.TimePeriodMinutes(minutes=42), cg.IntLiteral), ((1, 2, 3), cg.ArrayInitializer), ([1, 2, 3], cg.ArrayInitializer), -)) + ), +) def test_safe_exp__allowed_values(obj, expected_type): actual = cg.safe_exp(obj) assert isinstance(actual, expected_type) -@pytest.mark.parametrize("obj, expected_type", ( +@pytest.mark.parametrize( + "obj, expected_type", + ( (bool, ct.bool_), (int, ct.int32), (float, ct.float_), -)) + ), +) def test_safe_exp__allowed_types(obj, expected_type): actual = cg.safe_exp(obj) assert actual is expected_type -@pytest.mark.parametrize("obj, expected_error", ( +@pytest.mark.parametrize( + "obj, expected_error", + ( (cg.ID("foo"), "Object foo is an ID."), ((x for x in "foo"), r"Object <.*> is a coroutine."), (None, "Object is not an expression"), -)) + ), +) def test_safe_exp__invalid_values(obj, expected_error): with pytest.raises(ValueError, match=expected_error): cg.safe_exp(obj) class TestStatements: - @pytest.mark.parametrize("target, expected", ( - (cg.RawStatement("foo && bar"), "foo && bar"), - - (cg.ExpressionStatement("foo"), '"foo";'), - (cg.ExpressionStatement(42), '42;'), - - (cg.LineComment("The point of foo is..."), "// The point of foo is..."), - (cg.LineComment("Help help\nI'm being repressed"), "// Help help\n// I'm being repressed"), - + @pytest.mark.parametrize( + "target, expected", ( - cg.ProgmemAssignmentExpression(ct.uint16, "foo", "bar", None), - 'static const uint16_t foo[] PROGMEM = "bar"' - ) - )) + (cg.RawStatement("foo && bar"), "foo && bar"), + (cg.ExpressionStatement("foo"), '"foo";'), + (cg.ExpressionStatement(42), "42;"), + (cg.LineComment("The point of foo is..."), "// The point of foo is..."), + ( + cg.LineComment("Help help\nI'm being repressed"), + "// Help help\n// I'm being repressed", + ), + ( + cg.ProgmemAssignmentExpression(ct.uint16, "foo", "bar", None), + 'static const uint16_t foo[] PROGMEM = "bar"', + ), + ), + ) def test_str__simple(self, target: cg.Statement, expected: str): actual = str(target) diff --git a/tests/unit_tests/test_cpp_helpers.py b/tests/unit_tests/test_cpp_helpers.py index d8f32e7a5167..c6f37f6b5d37 100644 --- a/tests/unit_tests/test_cpp_helpers.py +++ b/tests/unit_tests/test_cpp_helpers.py @@ -15,11 +15,9 @@ def test_gpio_pin_expression__conf_is_none(monkeypatch): def test_gpio_pin_expression__new_pin(monkeypatch): - target = ch.gpio_pin_expression({ - const.CONF_NUMBER: 42, - const.CONF_MODE: "input", - const.CONF_INVERTED: False - }) + target = ch.gpio_pin_expression( + {const.CONF_NUMBER: 42, const.CONF_MODE: "input", const.CONF_INVERTED: False} + ) actual = next(target) @@ -71,10 +69,13 @@ def test_register_component__with_setup_priority(monkeypatch): add_mock = Mock() monkeypatch.setattr(ch, "add", add_mock) - target = ch.register_component(var, { - const.CONF_SETUP_PRIORITY: "123", - const.CONF_UPDATE_INTERVAL: "456", - }) + target = ch.register_component( + var, + { + const.CONF_SETUP_PRIORITY: "123", + const.CONF_UPDATE_INTERVAL: "456", + }, + ) actual = next(target) diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index 6e89a05bc254..00a6b08133d2 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -6,69 +6,89 @@ from esphome import helpers -@pytest.mark.parametrize("preferred_string, current_strings, expected", ( - ("foo", [], "foo"), - # TODO: Should this actually start at 1? - ("foo", ["foo"], "foo_2"), - ("foo", ("foo",), "foo_2"), - ("foo", ("foo", "foo_2"), "foo_3"), - ("foo", ("foo", "foo_2", "foo_2"), "foo_3"), -)) +@pytest.mark.parametrize( + "preferred_string, current_strings, expected", + ( + ("foo", [], "foo"), + # TODO: Should this actually start at 1? + ("foo", ["foo"], "foo_2"), + ("foo", ("foo",), "foo_2"), + ("foo", ("foo", "foo_2"), "foo_3"), + ("foo", ("foo", "foo_2", "foo_2"), "foo_3"), + ), +) def test_ensure_unique_string(preferred_string, current_strings, expected): actual = helpers.ensure_unique_string(preferred_string, current_strings) assert actual == expected -@pytest.mark.parametrize("text, expected", ( - ("foo", "foo"), - ("foo\nbar", "foo\nbar"), - ("foo\nbar\neek", "foo\n bar\neek"), -)) +@pytest.mark.parametrize( + "text, expected", + ( + ("foo", "foo"), + ("foo\nbar", "foo\nbar"), + ("foo\nbar\neek", "foo\n bar\neek"), + ), +) def test_indent_all_but_first_and_last(text, expected): actual = helpers.indent_all_but_first_and_last(text) assert actual == expected -@pytest.mark.parametrize("text, expected", ( - ("foo", [" foo"]), - ("foo\nbar", [" foo", " bar"]), - ("foo\nbar\neek", [" foo", " bar", " eek"]), -)) +@pytest.mark.parametrize( + "text, expected", + ( + ("foo", [" foo"]), + ("foo\nbar", [" foo", " bar"]), + ("foo\nbar\neek", [" foo", " bar", " eek"]), + ), +) def test_indent_list(text, expected): actual = helpers.indent_list(text) assert actual == expected -@pytest.mark.parametrize("text, expected", ( - ("foo", " foo"), - ("foo\nbar", " foo\n bar"), - ("foo\nbar\neek", " foo\n bar\n eek"), -)) +@pytest.mark.parametrize( + "text, expected", + ( + ("foo", " foo"), + ("foo\nbar", " foo\n bar"), + ("foo\nbar\neek", " foo\n bar\n eek"), + ), +) def test_indent(text, expected): actual = helpers.indent(text) assert actual == expected -@pytest.mark.parametrize("string, expected", ( - ("foo", '"foo"'), - ("foo\nbar", '"foo\\012bar"'), - ("foo\\bar", '"foo\\134bar"'), - ('foo "bar"', '"foo \\042bar\\042"'), - ('foo 🐍', '"foo \\360\\237\\220\\215"'), -)) +@pytest.mark.parametrize( + "string, expected", + ( + ("foo", '"foo"'), + ("foo\nbar", '"foo\\012bar"'), + ("foo\\bar", '"foo\\134bar"'), + ('foo "bar"', '"foo \\042bar\\042"'), + ("foo 🐍", '"foo \\360\\237\\220\\215"'), + ), +) def test_cpp_string_escape(string, expected): actual = helpers.cpp_string_escape(string) assert actual == expected -@pytest.mark.parametrize("host", ( - "127.0.0", "localhost", "127.0.0.b", -)) +@pytest.mark.parametrize( + "host", + ( + "127.0.0", + "localhost", + "127.0.0.b", + ), +) def test_is_ip_address__invalid(host): actual = helpers.is_ip_address(host) @@ -82,13 +102,16 @@ def test_is_ip_address__valid(value): assert actual is True -@pytest.mark.parametrize("var, value, default, expected", ( - ("FOO", None, False, False), - ("FOO", None, True, True), - ("FOO", "", False, False), - ("FOO", "Yes", False, True), - ("FOO", "123", False, True), -)) +@pytest.mark.parametrize( + "var, value, default, expected", + ( + ("FOO", None, False, False), + ("FOO", None, True, True), + ("FOO", "", False, False), + ("FOO", "Yes", False, True), + ("FOO", "123", False, True), + ), +) def test_get_bool_env(monkeypatch, var, value, default, expected): if value is None: monkeypatch.delenv(var, raising=False) @@ -100,10 +123,7 @@ def test_get_bool_env(monkeypatch, var, value, default, expected): assert actual == expected -@pytest.mark.parametrize("value, expected", ( - (None, False), - ("Yes", True) -)) +@pytest.mark.parametrize("value, expected", ((None, False), ("Yes", True))) def test_is_hassio(monkeypatch, value, expected): if value is None: monkeypatch.delenv("ESPHOME_IS_HASSIO", raising=False) @@ -185,20 +205,23 @@ def test_dst_does_not_exist(self, tmp_path, fixture_path): assert src.read_text() == dst.read_text() -@pytest.mark.parametrize("file1, file2, expected", ( - # Same file - ("file-a.txt", "file-a.txt", True), - # Different files, different size - ("file-a.txt", "file-b_1.txt", False), - # Different files, same size - ("file-a.txt", "file-c.txt", False), - # Same files - ("file-b_1.txt", "file-b_2.txt", True), - # Not a file - ("file-a.txt", "", False), - # File doesn't exist - ("file-a.txt", "file-d.txt", False), -)) +@pytest.mark.parametrize( + "file1, file2, expected", + ( + # Same file + ("file-a.txt", "file-a.txt", True), + # Different files, different size + ("file-a.txt", "file-b_1.txt", False), + # Different files, same size + ("file-a.txt", "file-c.txt", False), + # Same files + ("file-b_1.txt", "file-b_2.txt", True), + # Not a file + ("file-a.txt", "", False), + # File doesn't exist + ("file-a.txt", "file-d.txt", False), + ), +) def test_file_compare(fixture_path, file1, file2, expected): path1 = fixture_path / "helpers" / file1 path2 = fixture_path / "helpers" / file2 diff --git a/tests/unit_tests/test_pins.py b/tests/unit_tests/test_pins.py index 7d68181addbd..6bc6f4d766f1 100644 --- a/tests/unit_tests/test_pins.py +++ b/tests/unit_tests/test_pins.py @@ -15,12 +15,12 @@ MOCK_ESP8266_BOARD_ID = "_mock_esp8266" -MOCK_ESP8266_PINS = {'X0': 16, 'X1': 5, 'X2': 4, 'LED': 2} +MOCK_ESP8266_PINS = {"X0": 16, "X1": 5, "X2": 4, "LED": 2} MOCK_ESP8266_BOARD_ALIAS_ID = "_mock_esp8266_alias" MOCK_ESP8266_FLASH_SIZE = pins.FLASH_SIZE_2_MB MOCK_ESP32_BOARD_ID = "_mock_esp32" -MOCK_ESP32_PINS = {'Y0': 12, 'Y1': 8, 'Y2': 3, 'LED': 9, "A0": 8} +MOCK_ESP32_PINS = {"Y0": 12, "Y1": 8, "Y2": 3, "LED": 9, "A0": 8} MOCK_ESP32_BOARD_ALIAS_ID = "_mock_esp32_alias" UNKNOWN_PLATFORM = "STM32" @@ -68,10 +68,13 @@ def core_esp32(core): class Test_lookup_pin: - @pytest.mark.parametrize("value, expected", ( + @pytest.mark.parametrize( + "value, expected", + ( ("X1", 5), ("MOSI", 13), - )) + ), + ) def test_valid_esp8266_pin(self, core_esp8266, value, expected): actual = pins._lookup_pin(value) @@ -84,11 +87,14 @@ def test_valid_esp8266_pin_alias(self, core_esp8266): assert actual == 4 - @pytest.mark.parametrize("value, expected", ( + @pytest.mark.parametrize( + "value, expected", + ( ("Y1", 8), ("A0", 8), ("MOSI", 23), - )) + ), + ) def test_valid_esp32_pin(self, core_esp32, value, expected): actual = pins._lookup_pin(value) @@ -102,7 +108,9 @@ def test_valid_32_pin_alias(self, core_esp32): assert actual == 3 def test_invalid_pin(self, core_esp8266): - with pytest.raises(Invalid, match="Cannot resolve pin name 'X42' for board _mock_esp8266."): + with pytest.raises( + Invalid, match="Cannot resolve pin name 'X42' for board _mock_esp8266." + ): pins._lookup_pin("X42") def test_unsupported_platform(self, core): @@ -113,13 +121,16 @@ def test_unsupported_platform(self, core): class Test_translate_pin: - @pytest.mark.parametrize("value, expected", ( + @pytest.mark.parametrize( + "value, expected", + ( (2, 2), ("3", 3), ("GPIO4", 4), ("TX", 1), ("Y0", 12), - )) + ), + ) def test_valid_values(self, core_esp32, value, expected): actual = pins._translate_pin(value) @@ -137,7 +148,9 @@ def test_esp32_valid(self, core_esp32): assert actual == 22 - @pytest.mark.parametrize("value, match", ( + @pytest.mark.parametrize( + "value, match", + ( (-1, "ESP32: Invalid pin number: -1"), (40, "ESP32: Invalid pin number: 40"), (6, "This pin cannot be used on ESP32s and"), @@ -150,7 +163,8 @@ def test_esp32_valid(self, core_esp32): (29, "The pin GPIO29 is not usable on ESP32s"), (30, "The pin GPIO30 is not usable on ESP32s"), (31, "The pin GPIO31 is not usable on ESP32s"), - )) + ), + ) def test_esp32_invalid_pin(self, core_esp32, value, match): with pytest.raises(Invalid, match=match): pins.validate_gpio_pin(value) @@ -168,14 +182,17 @@ def test_esp8266_valid(self, core_esp8266): assert actual == 12 - @pytest.mark.parametrize("value, match", ( + @pytest.mark.parametrize( + "value, match", + ( (-1, "ESP8266: Invalid pin number: -1"), (18, "ESP8266: Invalid pin number: 18"), (6, "This pin cannot be used on ESP8266s and"), (7, "This pin cannot be used on ESP8266s and"), (8, "This pin cannot be used on ESP8266s and"), (11, "This pin cannot be used on ESP8266s and"), - )) + ), + ) def test_esp8266_invalid_pin(self, core_esp8266, value, match): with pytest.raises(Invalid, match=match): pins.validate_gpio_pin(value) @@ -196,18 +213,19 @@ def test_unknown_device(self, core): class Test_input_pin: - @pytest.mark.parametrize("value, expected", ( - ("X0", 16), - )) + @pytest.mark.parametrize("value, expected", (("X0", 16),)) def test_valid_esp8266_values(self, core_esp8266, value, expected): actual = pins.input_pin(value) assert actual == expected - @pytest.mark.parametrize("value, expected", ( + @pytest.mark.parametrize( + "value, expected", + ( ("Y0", 12), (17, 17), - )) + ), + ) def test_valid_esp32_values(self, core_esp32, value, expected): actual = pins.input_pin(value) @@ -226,18 +244,19 @@ def test_unknown_platform(self, core): class Test_input_pullup_pin: - @pytest.mark.parametrize("value, expected", ( - ("X0", 16), - )) + @pytest.mark.parametrize("value, expected", (("X0", 16),)) def test_valid_esp8266_values(self, core_esp8266, value, expected): actual = pins.input_pullup_pin(value) assert actual == expected - @pytest.mark.parametrize("value, expected", ( + @pytest.mark.parametrize( + "value, expected", + ( ("Y0", 12), (17, 17), - )) + ), + ) def test_valid_esp32_values(self, core_esp32, value, expected): actual = pins.input_pullup_pin(value) @@ -256,18 +275,19 @@ def test_unknown_platform(self, core): class Test_output_pin: - @pytest.mark.parametrize("value, expected", ( - ("X0", 16), - )) + @pytest.mark.parametrize("value, expected", (("X0", 16),)) def test_valid_esp8266_values(self, core_esp8266, value, expected): actual = pins.output_pin(value) assert actual == expected - @pytest.mark.parametrize("value, expected", ( + @pytest.mark.parametrize( + "value, expected", + ( ("Y0", 12), (17, 17), - )) + ), + ) def test_valid_esp32_values(self, core_esp32, value, expected): actual = pins.output_pin(value) @@ -291,18 +311,19 @@ def test_unknown_platform(self, core): class Test_analog_pin: - @pytest.mark.parametrize("value, expected", ( - (17, 17), - )) + @pytest.mark.parametrize("value, expected", ((17, 17),)) def test_valid_esp8266_values(self, core_esp8266, value, expected): actual = pins.analog_pin(value) assert actual == expected - @pytest.mark.parametrize("value, expected", ( + @pytest.mark.parametrize( + "value, expected", + ( (32, 32), (39, 39), - )) + ), + ) def test_valid_esp32_values(self, core_esp32, value, expected): actual = pins.analog_pin(value) diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 2baff80eddf4..6c952608d475 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -1,4 +1,4 @@ -""" Tests for the wizard.py file """ +"""Tests for the wizard.py file.""" import esphome.wizard as wz import pytest @@ -14,7 +14,7 @@ def default_config(): "board": "test_board", "ssid": "test_ssid", "psk": "test_psk", - "password": "" + "password": "", } @@ -35,13 +35,13 @@ def test_sanitize_quotes_replaces_with_escaped_char(): The sanitize_quotes function should replace double quotes with their escaped equivalents """ # Given - input_str = "\"key\": \"value\"" + input_str = '"key": "value"' # When output_str = wz.sanitize_double_quotes(input_str) # Then - assert output_str == "\\\"key\\\": \\\"value\\\"" + assert output_str == '\\"key\\": \\"value\\"' def test_config_file_fallback_ap_includes_descriptive_name(default_config): @@ -55,7 +55,7 @@ def test_config_file_fallback_ap_includes_descriptive_name(default_config): config = wz.wizard_file(**default_config) # Then - assert f"ssid: \"Test Node Fallback Hotspot\"" in config + assert 'ssid: "Test Node Fallback Hotspot"' in config def test_config_file_fallback_ap_name_less_than_32_chars(default_config): @@ -70,7 +70,7 @@ def test_config_file_fallback_ap_name_less_than_32_chars(default_config): config = wz.wizard_file(**default_config) # Then - assert f"ssid: \"A Very Long Name For This Node\"" in config + assert 'ssid: "A Very Long Name For This Node"' in config def test_config_file_should_include_ota(default_config): @@ -115,7 +115,9 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): assert f"platform: {default_config['platform']}" in generated_config -def test_wizard_write_defaults_platform_from_board_esp8266(default_config, tmp_path, monkeypatch): +def test_wizard_write_defaults_platform_from_board_esp8266( + default_config, tmp_path, monkeypatch +): """ If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards """ @@ -133,7 +135,9 @@ def test_wizard_write_defaults_platform_from_board_esp8266(default_config, tmp_p assert "platform: ESP8266" in generated_config -def test_wizard_write_defaults_platform_from_board_esp32(default_config, tmp_path, monkeypatch): +def test_wizard_write_defaults_platform_from_board_esp32( + default_config, tmp_path, monkeypatch +): """ If the platform is not explicitly set, use "ESP32" if the board is not one of the ESP8266 boards """ @@ -167,7 +171,9 @@ def test_safe_print_step_prints_step_number_and_description(monkeypatch): # Then # Collect arguments to all safe_print() calls (substituting "" for any empty ones) - all_args = [call.args[0] if len(call.args) else "" for call in wz.safe_print.call_args_list] + all_args = [ + call.args[0] if len(call.args) else "" for call in wz.safe_print.call_args_list + ] assert any(step_desc == arg for arg in all_args) assert any(f"STEP {step_num}" in arg for arg in all_args) @@ -212,7 +218,7 @@ def test_strip_accents_removes_diacritics(): """ # Given - input_str = u"Kühne" + input_str = "Kühne" expected_str = "Kuhne" # When @@ -264,7 +270,7 @@ def test_wizard_accepts_default_answers_esp8266(tmpdir, monkeypatch, wizard_answ monkeypatch.setattr("builtins.input", input_mock) monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) monkeypatch.setattr(wz, "sleep", lambda _: 0) - monkeypatch.setattr(wz, "wizard_write", MagicMock()) + monkeypatch.setattr(wz, "wizard_write", MagicMock()) # When retval = wz.wizard(str(config_file)) @@ -286,7 +292,7 @@ def test_wizard_accepts_default_answers_esp32(tmpdir, monkeypatch, wizard_answer monkeypatch.setattr("builtins.input", input_mock) monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) monkeypatch.setattr(wz, "sleep", lambda _: 0) - monkeypatch.setattr(wz, "wizard_write", MagicMock()) + monkeypatch.setattr(wz, "wizard_write", MagicMock()) # When retval = wz.wizard(str(config_file)) @@ -306,14 +312,16 @@ def test_wizard_offers_better_node_name(tmpdir, monkeypatch, wizard_answers): # Given wizard_answers[0] = "Küche #2" expected_name = "kuche_2" - monkeypatch.setattr(wz, "default_input", MagicMock(side_effect=lambda _, default: default)) + monkeypatch.setattr( + wz, "default_input", MagicMock(side_effect=lambda _, default: default) + ) config_file = tmpdir.join("test.yaml") input_mock = MagicMock(side_effect=wizard_answers) monkeypatch.setattr("builtins.input", input_mock) monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) monkeypatch.setattr(wz, "sleep", lambda _: 0) - monkeypatch.setattr(wz, "wizard_write", MagicMock()) + monkeypatch.setattr(wz, "wizard_write", MagicMock()) # When retval = wz.wizard(str(config_file)) @@ -336,7 +344,7 @@ def test_wizard_requires_correct_platform(tmpdir, monkeypatch, wizard_answers): monkeypatch.setattr("builtins.input", input_mock) monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) monkeypatch.setattr(wz, "sleep", lambda _: 0) - monkeypatch.setattr(wz, "wizard_write", MagicMock()) + monkeypatch.setattr(wz, "wizard_write", MagicMock()) # When retval = wz.wizard(str(config_file)) @@ -358,7 +366,7 @@ def test_wizard_requires_correct_board(tmpdir, monkeypatch, wizard_answers): monkeypatch.setattr("builtins.input", input_mock) monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) monkeypatch.setattr(wz, "sleep", lambda _: 0) - monkeypatch.setattr(wz, "wizard_write", MagicMock()) + monkeypatch.setattr(wz, "wizard_write", MagicMock()) # When retval = wz.wizard(str(config_file)) @@ -380,7 +388,7 @@ def test_wizard_requires_valid_ssid(tmpdir, monkeypatch, wizard_answers): monkeypatch.setattr("builtins.input", input_mock) monkeypatch.setattr(wz, "safe_print", lambda t=None: 0) monkeypatch.setattr(wz, "sleep", lambda _: 0) - monkeypatch.setattr(wz, "wizard_write", MagicMock()) + monkeypatch.setattr(wz, "wizard_write", MagicMock()) # When retval = wz.wizard(str(config_file))