Compare commits

...

496 Commits

Author SHA1 Message Date
Otto Winter
596c334fcb Bump version to v1.12.0b1 2019-03-13 18:58:54 +01:00
Otto Winter
ca4450858f Add auto-issue closer 2019-03-13 18:30:45 +01:00
Otto Winter
f3ec83fe31 Add ESP32 Camera (#475)
* Add ESP32 Camera

* Fixes

* Updates

* Fix substitutions not working for non-ASCII

* Update docker base image to 1.3.0
2019-03-13 16:40:09 +01:00
Florian Gareis
c4ada8c9f0 Add color to login error for better visability (#478) 2019-03-08 12:42:22 +01:00
puuu
23df5d8af7 Support SDS011 component. (#467)
* Support SDS011 component.

* improve if condition

* Check update interval is multiple of minute

* do not allow update intervals longer than 30 min

* fix sensor schema name

* remove query_mode

* Warn if rx_only mode used together with update interval

* Allow update intervals below 1min

Messed that up before, as the docs say update intervals below 1min are allowed

* Use update interval in minutes

* use set_update_interval_min() to set update interval
2019-03-06 12:39:52 +01:00
Otto Winter
289acade1e vol.Schema -> cv.Schema 2019-03-05 15:06:38 +01:00
Otto Winter
a99f99779a Use double quotes for wizard
Fixes https://github.com/esphome/issues/issues/81
2019-03-05 14:14:38 +01:00
Otto Winter
5c14ca030a Add Wifi info text sensor (#473)
* Add WiFi Info text sensor

* Lint

* Fix register

* Add newline at end of file
2019-03-05 14:05:51 +01:00
Otto Winter
0fc6a027a7 Add copy output platform (#472) 2019-03-05 13:54:33 +01:00
Otto Winter
3c0d97ef69 Add connected condition (#474)
* Add WiFi/MQTT/API connected condition

* Add tests

* Fix namespace

* Update api.py
2019-03-05 13:54:14 +01:00
Otto Winter
5b4f98d414 Revert "Remove piolibdeps cache"
This reverts commit e009f21a72.
2019-03-05 13:53:09 +01:00
Otto Winter
7ebfcd3807 Add for to binary sensor conditions (#471) 2019-03-05 13:51:53 +01:00
Otto Winter
3ef0634dd2 Upgrade ESP32 core to 1.0.1 (#470)
* Upgrade ESP32 core to 1.0.1

Ref https://github.com/espressif/arduino-esp32/issues/2540

* Undo remove

* Add i2c debugging

* Lint
2019-03-05 13:44:51 +01:00
Otto Winter
0928c9739f Fix build 2019-03-05 13:44:38 +01:00
Otto Winter
e009f21a72 Remove piolibdeps cache
We cannot cache esphome-core
2019-03-05 13:28:18 +01:00
Otto Winter
606c412616 Cache travis folders 2019-03-05 13:27:38 +01:00
Otto Winter
975b5127d6 Allow Arduino Core 2.5.0 for ESP8266 (#469) 2019-03-05 13:25:59 +01:00
Otto Winter
60c9ffef30 Fix build 2019-03-03 19:29:10 +01:00
Otto Winter
99861259d7 Add MCP23017 (#466)
* Add MCP23017

* Add test
2019-03-03 16:51:55 +01:00
Otto Winter
067ec30c56 Add relative_url, streamer_mode, status_use_ping dashboard options (#461)
* Add relative_url, streamer_mode, status_use_ping dashboard options

Additionally Hass.io now stores all build files in /data, so that snapshots no longer get huge.

* Lint

* Lint

* Replace tabs with spaces
2019-03-03 16:50:06 +01:00
Otto Winter
5a102c2ab7 Rewrite interrupt components (#464)
* Rewrite intterupt components

* Fix test
2019-03-03 16:47:10 +01:00
Otto Winter
4b017e2096 Add WiFi/MQTT/API connected condition (#465)
* Add WiFi/MQTT/API connected condition

* Add tests

* Fix namespace
2019-03-03 16:45:56 +01:00
Otto Winter
8495ce96a3 Lint
tornado got updated, broke pylint
2019-03-03 16:43:51 +01:00
Marco
55caf4f648 Update ads1115.py (#468)
GAIN setting is pointing to the wrong value
2019-03-01 16:43:51 +01:00
Otto Winter
c123f0091d Upgrade pylint on python 3 to fix travis 2019-02-28 10:33:22 +01:00
Otto Winter
7c65d44976 Add rotary encoder min/max value (#463) 2019-02-28 10:17:38 +01:00
Otto Winter
5b8d12a80c Enable i2c scanning by default (#462) 2019-02-28 10:16:19 +01:00
Otto Winter
3951a2b22a Fix os.symlink on Windows (#460) 2019-02-28 10:15:57 +01:00
Otto Winter
69a74a30e8 Lint 2019-02-28 10:14:28 +01:00
Otto Winter
f3ee5b55e9 Add warning if esphome-core dev used without esphome dev 2019-02-28 10:11:05 +01:00
Otto Winter
f6cc9f7caa Merge branch 'master' into dev 2019-02-26 23:12:06 +01:00
Otto Winter
a5d0ecdb13 Bump version to v1.11.2 2019-02-26 23:04:22 +01:00
Otto Winter
71cbc9cfb0 Fix mDNS library added only with OTA (#451) 2019-02-26 23:04:20 +01:00
Otto Winter
88625c656d Remove automatic update check (#457)
* Remove automatic update check

* Lint
2019-02-26 23:04:19 +01:00
Otto Winter
971b15ac67 Allow non-pullup pins for dallas (#456) 2019-02-26 23:04:19 +01:00
Otto Winter
a4edcc48ca Lint 2019-02-26 19:46:58 +01:00
Otto Winter
1778dd4df9 Add linear calibration sensor filter (#454)
* Add linear calibrate filter

* Remove filter_nan

* Add test
2019-02-26 19:38:39 +01:00
Otto Winter
311e837196 Add native API user-defined services (#453) 2019-02-26 19:38:28 +01:00
Otto Winter
3b00cfd6c4 Convert automation engine to use variadic templates (#452) 2019-02-26 19:28:11 +01:00
Otto Winter
1c7ca4bc6f Recommend similar keys for spelling errors (#458)
* Recommend similar keys for spelling errors

Fixes https://github.com/esphome/feature-requests/issues/68

* Fixes

* Fix
2019-02-26 19:22:33 +01:00
mtl010957
38e7b597d6 Add handling for min power output setting (#448)
* Add handling for min power output setting

* Fix line length error
2019-02-26 18:35:45 +01:00
Otto Winter
808ee19180 Fix mDNS library added only with OTA (#451) 2019-02-26 18:34:47 +01:00
Otto Winter
c2a0c22bd9 Automatically hide secrets in validation (#455)
* Hide secrets in validation

* Lint
2019-02-26 18:32:20 +01:00
Otto Winter
e785ad5401 Remove automatic update check (#457)
* Remove automatic update check

* Lint
2019-02-26 18:31:40 +01:00
Otto Winter
bacddc3673 Add restore state from flash option (#459) 2019-02-26 16:55:37 +01:00
Otto Winter
7dd0dabaf5 Allow non-pullup pins for dallas (#456) 2019-02-26 16:39:20 +01:00
Michiel van Turnhout
92f8b043ce Add MPR121 Capacitive Touch Sensor (#449)
* module mpr121

* added mpr121 sensor and binary sensor

* added tests

* removed CONF_CHANNELS, it is not supported in the esphome-code mpr121
code

* changed namespace for mpr121 to binary_sensor

* Update esphome/components/binary_sensor/mpr121.py

Co-Authored-By: mvturnho <qris.online@gmail.com>

* fixed class names

* fix lint errors
2019-02-24 21:48:28 +01:00
Otto Winter
2e5fd7e90d Merge branch 'master' into dev 2019-02-23 20:25:28 +01:00
Otto Winter
407c46cb03 Bump version to v1.11.1 2019-02-23 20:13:49 +01:00
Otto Winter
12ce448f2d Merge branch 'master' into dev 2019-02-22 22:06:36 +01:00
Otto Winter
340530566c Bump version to v1.11.0 2019-02-22 21:59:06 +01:00
Otto Winter
f4a55eafd7 Merge branch 'beta' 2019-02-22 21:46:48 +01:00
Otto Winter
fc8f270a9f Improve dashboard setup wizard (#450) 2019-02-22 21:20:27 +01:00
Otto Winter
e7ce8f7a13 Improve dashboard setup wizard (#450) 2019-02-22 21:18:56 +01:00
Otto Winter
3d1cce2a29 Add report_position test
Fixes https://github.com/esphome/issues/issues/46
2019-02-22 17:43:30 +01:00
Otto Winter
6be47488a4 Bump version to v1.11.0b3 2019-02-21 18:35:04 +01:00
Otto Winter
5b94bb894e Allow use of arduino core v2.5.0 on ESP8266 (#446)
* Allow use of arduino core v2.5.0 on ESP8266

It's very unstable, but you can try if you want

* Fix
2019-02-21 18:35:01 +01:00
Otto Winter
26aa399bea Allow i2c on non-pullup pins (#447) 2019-02-21 18:35:00 +01:00
Otto Winter
af0c213024 Allow use of arduino core v2.5.0 on ESP8266 (#446)
* Allow use of arduino core v2.5.0 on ESP8266

It's very unstable, but you can try if you want

* Fix
2019-02-21 18:16:00 +01:00
Otto Winter
88e036ddb2 Allow i2c on non-pullup pins (#447) 2019-02-21 18:15:45 +01:00
Otto Winter
8012de3ba4 Revert "Merge remote-tracking branch 'origin/master' into dev"
This reverts commit 52750d933b, reversing
changes made to 7e7a85abfd.
2019-02-20 13:21:49 +01:00
Otto Winter
e2b1d5438d Bump version to v1.11.0b2 2019-02-20 12:50:28 +01:00
Otto Winter
fe66f93a01 Fix MQTT log topic level (#445) 2019-02-20 12:50:24 +01:00
Florian Gareis
581dffe2d4 Remove unnecessary wrapper (#444) 2019-02-20 12:50:24 +01:00
Florian Gareis
6f22006bf7 Remove duplicate scrollbar & move scrollbar (#443)
* Remove duplicate scrollbar

* Move scrolling from modal-content to log-container

* Replace css autoscroll with stable js autoscroll
2019-02-20 12:50:24 +01:00
Otto Winter
fe54700687 Fix custom components not registered (#441) 2019-02-20 12:50:24 +01:00
Otto Winter
f0c0131ed1 Fix MQTT log topic level (#445) 2019-02-20 12:38:09 +01:00
Otto Winter
52750d933b Merge remote-tracking branch 'origin/master' into dev 2019-02-20 12:22:43 +01:00
Florian Gareis
7e7a85abfd Remove unnecessary wrapper (#444) 2019-02-19 20:46:23 +01:00
Florian Gareis
c1b8107aaf Remove duplicate scrollbar & move scrollbar (#443)
* Remove duplicate scrollbar

* Move scrolling from modal-content to log-container

* Replace css autoscroll with stable js autoscroll
2019-02-19 20:30:12 +01:00
Otto Winter
0bdcce609f Re-remove aarch64
Tried it again and platformio doesn't work
2019-02-19 15:03:59 +01:00
Otto Winter
6b702f8014 Fix custom components not registered (#441) 2019-02-18 14:59:35 +01:00
Otto Winter
078ab26fd2 Bump dev version to 1.12.0-dev 2019-02-18 12:10:05 +01:00
Otto Winter
efe81e0856 Bump version to v1.11.0b1 2019-02-17 20:23:20 +01:00
Otto Winter
41d637affe Add wait_until action (#440) 2019-02-17 19:27:33 +01:00
Florian Gareis
10cc11bab1 Include tapTarget html element only when needed (#439) 2019-02-17 18:42:44 +01:00
Otto Winter
34ca5d6d8a Update base image to 1.2.1 2019-02-17 15:54:15 +01:00
Florian Gareis
b27c778cb7 Fix dashboard style issues (#437)
* Update dashboard copyright year

* Fix overflow and select color issue

* Make upload and compile modals not dismissible on outside click

* Fix to small dropdown
2019-02-17 15:31:41 +01:00
Otto Winter
f311a1bb30 Add display page abstraction (#435) 2019-02-17 00:35:23 +01:00
Otto Winter
7a450ed41c Add hidden option to wifi networks (#436) 2019-02-16 16:49:27 +01:00
Otto Winter
944f0169cb Add light partition platform (#434) 2019-02-16 16:47:23 +01:00
Otto Winter
4da0e0c223 Add text_sensor.template.publish action (#433)
* Add text_sensor.template.publish action

* Fix

* Add test
2019-02-16 16:47:05 +01:00
Otto Winter
52c0b45f41 Add RC5 IR code support (#432) 2019-02-16 15:30:59 +01:00
Otto Winter
fcef3be5c7 Template lambda is optional 2019-02-16 13:12:49 +01:00
Otto Winter
ef7b936d60 Add ADC sensor 2019-02-15 13:22:31 +01:00
Otto Winter
e0eac6ba25 Make esphomeyaml rename a bit more graceful 2019-02-15 10:25:56 +01:00
Otto Winter
50af0da13f Add esphomeyaml legacy script 2019-02-15 10:19:18 +01:00
Otto Winter
a8d87a1fee Update to 1.1.0 docker base image 2019-02-14 16:07:28 +01:00
Otto Winter
a66ed437d6 Include common components for compiles (#431) 2019-02-14 14:46:10 +01:00
Otto Winter
b95f53a7c7 Update README.md 2019-02-14 13:49:22 +01:00
Otto Winter
d7eacb2a2f Fix docker hub build 2019-02-13 21:40:10 +01:00
Otto Winter
41ec337ba0 Try to fix docker hub build 2019-02-13 21:29:08 +01:00
Otto Winter
5e61014e98 Update Gitlab CI 2019-02-13 21:14:03 +01:00
Brad Davidson
0a4d49accb Fix missing whitespace in key migration message 2019-02-13 09:42:54 -08:00
Otto Winter
3d9301a0f7 Rename esphomeyaml to esphome (#426)
* Rename

* Update

* Add migration

* Fix

* Fix dashboard

* Change test

* Fixes

* Code cleanup

* Fix import order

* Update

* Automate docker builds

* Shellcheck
2019-02-13 16:54:02 +01:00
Brandon Davidson
1b8d242505 Enable use of alternate hardware UARTs for logging (#427)
* Enable use of alternate hardware UARTs for logging

Enable use of Serial1 on ESP8266 and Serial1/Serial2 on ESP32 for logging.
This is frequently done on ESP8266 to allow use of Serial for UART TX+RX,
while maintaining logging output on Serial1 which is TX-only via GPIO2.

* ESPHOMELIB_UART -> UART_SELECTION_UART; HW_UART -> HARDWARE_UART

* Add test3 to travis; remove test4

* Set DEBUG_ESP_PORT based on logger UART setting
2019-02-13 11:20:22 +01:00
Jesse Hills
463ad6f94b Add support for JVC remote transmitting and receiving (#423)
* Add support for JVC remote transmitting and receiving

* Add missing import

* Fix line length
2019-02-11 18:09:31 +01:00
Otto Winter
e4e315f723 Revert "Upgrade espressif32 package to 1.6.0 (#355)" (#422)
* Revert "Upgrade espressif32 package to 1.6.0 (#355)"

This reverts commit e347cb538d.

* Fix revert

* Fix
2019-02-11 16:50:10 +01:00
Otto Winter
7fecec128f Make dout the default flash mode (#420) 2019-02-11 16:49:12 +01:00
Otto Winter
a15b647d13 Rework UART component for fixes (#419) 2019-02-11 15:50:12 +01:00
Otto Winter
feab956ea9 Add use_address (#417) 2019-02-10 23:55:13 +01:00
Otto Winter
4b7a41922c Add template publish actions and switch triggers (#391)
* Add template publish actions and switch triggers

* Fix

* Add tests

* Add cover

* Lint

* Fix

* Fix
2019-02-10 23:35:07 +01:00
Otto Winter
61762bf299 Replace optimistic with Assumed State (#394)
* Replace optimistic with Assumed State

* Add tests

* Fix

* Lint
2019-02-10 23:28:56 +01:00
Otto Winter
6eb769e6d5 Auto-Redact personal information from logs (#421) 2019-02-10 20:30:05 +01:00
Otto Winter
4c8bd7a70c Fix build 2019-02-10 20:29:06 +01:00
Otto Winter
d9cf91210e Add local mDNS responder for .local (#386)
* Add local mDNS responder

* Fix

* Use mDNS in dashboard status

* Lint

* Lint

* Fix test

* Remove hostname

* Fix use enum

* Lint
2019-02-10 16:57:34 +01:00
Otto Winter
fcf5da66c0 Add Switch Interlocking (#411) 2019-02-10 16:47:39 +01:00
Otto Winter
b6edbf9d0b Better error messages for OTA (#418)
* Better error messages for OTA

* Update espota2.py
2019-02-10 16:43:44 +01:00
Otto Winter
596f995af8 Store Raw Remote Codes in PROGMEM (#392)
* Store Raw Remote Codes in PROGMEM

* Lint

* Lint

* Fix
2019-02-10 16:41:12 +01:00
Otto Winter
8ce176aaba Allow pins 9&10 for PWM (#410)
Ref https://github.com/esp8266/Arduino/pull/5055
2019-02-10 16:32:02 +01:00
Otto Winter
f185ba7a21 Add Homeassistant Binary Sensor (#409) 2019-02-10 16:31:17 +01:00
Otto Winter
7c28eca6de Print error when mqtt.publish used without MQTT enabled (#408)
Fixes https://github.com/esphome/issues/issues/40
2019-02-10 16:30:17 +01:00
Otto Winter
9fa95a9a21 Lint 2019-02-10 16:22:12 +01:00
Otto Winter
6ccb68aaf1 Fix custom components 2019-02-10 14:15:20 +01:00
Otto Winter
6a76a3642e Fix SSID must be ascii 2019-02-08 17:52:42 +01:00
Otto Winter
a50eb3579a Validate neopixelbus method (#398)
* Validate NeoPixelBus Method Pin

* Add path

* Prefix GPIO
2019-02-03 21:18:45 +01:00
Otto Winter
4a74027848 Miscellaneous Fixes 2019-02-03 20:46:18 +01:00
Otto Winter
98bdfc821e Disable travis test for Python 3
It hasn't found any bugs so far - and it takes a lot of time
2019-01-29 17:46:02 +01:00
Otto Winter
176c712eeb Disable MQTT if not used (#373)
* Disable MQTT if not used

* Lint
2019-01-29 17:29:21 +01:00
Otto Winter
7b26ecc0dc Remove Heartbeat Filter (#393)
* Remove Heartbeat Filter

* Fix tests
2019-01-29 16:45:50 +01:00
Otto Winter
92e909568c Fix Custom Components No Name (#395)
* Fix Custom Components No Name

Fixes https://github.com/esphome/ESPHome-Core/issues/445

* Fix
2019-01-29 16:39:29 +01:00
Marcin Jaworski
e3f7e0d14a Generate variable for each custom component id (#382)
* Generate variable for each custom component id

* Change variable to Pvariable for custom components
2019-01-29 16:21:00 +01:00
Marcin Jaworski
5fcda34c45 Includes should be relative to the src directory, not main.cpp file (#390) 2019-01-29 16:12:38 +01:00
Otto Winter
bd23584073 Better error message 2019-01-27 16:38:29 +01:00
Antoine GRÉA
6d6876ca39 Adding SI7021 sensor to config validation (#375) 2019-01-25 17:55:29 +01:00
Otto Winter
9ebf6439b2 Revert "Enable aarch64"
This reverts commit 7153c7a941.
2019-01-22 21:34:45 +01:00
Otto Winter
1404d39ffe Enable aarch64
The repo just mirrors armhf, thanks @Frenck !
2019-01-22 20:44:11 +01:00
Otto Winter
7153c7a941 Enable aarch64
The repo just mirrors armhf, thanks @Frenck !
2019-01-22 20:43:45 +01:00
Otto Winter
3437e23c8e Disable platformio LDF (#352)
* Disable platformio LDF

* Use required build flags
2019-01-19 22:14:46 +01:00
Otto Winter
eb39add6fc Fix nginx closing WebSocket connection after 60 seconds (#370)
* Test

* Add keepalive

* Revert "Add keepalive"

This reverts commit 8b92198122.

* Update dashboard.py

* Revert

* Lint
2019-01-19 22:10:10 +01:00
Otto Winter
e13bffbb2f Fix dashboard password with python 3 (#339)
* Fix dashboard password with python 3

* Lint
2019-01-19 22:09:46 +01:00
Otto Winter
e347cb538d Upgrade espressif32 package to 1.6.0 (#355)
* Upgrade espressif32 package to 1.6.0

* Remove AsyncTCP pinning

* Lint

* Fix AsyncTCP version

* Fix test2
2019-01-19 21:44:47 +01:00
Robert Schütz
d799c03f0c call platformio and esptool using subprocess if $ESPHOME_USE_SUBPROCESS is set (#359)
* call platformio and esptool using subprocess if $ESPHOME_USE_SUBPROCESS is set

* Update setup.py
2019-01-19 17:54:41 +01:00
Otto Winter
c86675f644 Fix custom output requiring type (#344)
* Fix custom output requiring type

Fixes #343

* Revert "Fix custom output requiring type"

This reverts commit 37f995d32a.

* Fix
2019-01-19 16:53:54 +01:00
Otto Winter
a00fe09c66 Remove 2019-01-19 16:52:31 +01:00
Otto Winter
e0fe0a2835 Remove deep sleep run_cycles (#353)
* Remove deep sleep run_cycles

* Fix test
2019-01-19 16:51:20 +01:00
Otto Winter
e1f48b5028 Rmove DNS1,DNS2 inclusive (#357) 2019-01-19 16:50:31 +01:00
Otto Winter
e4a2677de4 ESP8266 Better Exception Code Names (#358) 2019-01-19 16:50:17 +01:00
Otto Winter
ab40156c16 Fix ESP32 BLE tracker scan interval in seconds (#367)
Fixes #361
2019-01-19 16:48:54 +01:00
Otto Winter
804a1ed7b3 Upgrade HassIO Ubuntu Base to 2.2.1 (#368) 2019-01-19 16:48:35 +01:00
Otto Winter
4de98fd782 Fix Non-ASCII characters being escaped if in wrong locale (#369) 2019-01-19 16:48:16 +01:00
Otto Winter
d8fd5e9bb4 Use strict string mode for WiFi password (#351)
Fixes #350
2019-01-18 11:21:08 +01:00
Otto Winter
8424f9c34b Fix expire_after requires MQTT (#354)
Fixes https://github.com/OttoWinter/esphomelib/issues/372
2019-01-18 11:16:53 +01:00
Robert Schütz
70016d7641 Add pyserial to install_requires (#348) 2019-01-17 09:40:31 +01:00
Otto Winter
7ced977f2a Remove aarch64 from edge 2019-01-16 11:31:27 +01:00
Otto Winter
5ac9b8d545 Allow IPv4 addresses for SNTP servers (#340)
Fixes https://github.com/OttoWinter/esphomelib/issues/395
2019-01-15 20:13:20 +01:00
Otto Winter
a683108b5d Fix install pillow in docker image (#338)
Fixes https://github.com/OttoWinter/esphomeyaml/issues/266
2019-01-15 20:13:09 +01:00
Robert Schütz
54dd9e94ab typing is only required for python < 3.5 (#341)
Since 3.5, it is included in the standard library.
2019-01-15 16:50:50 +01:00
Otto Winter
4f5389998f Add homeassistant.service test 2019-01-14 11:09:32 +01:00
Otto Winter
8a61c21051 Use noreferrer for dashboard links
Was showing up in GA, and this feels creepy. I don't want to know your HA installation address
2019-01-14 11:09:23 +01:00
Otto Winter
b71b14cd06 Bump HassIO version to v1.10.1 2019-01-13 19:06:09 +01:00
Otto Winter
9b2b7dabd1 Bump HassIO version to v1.10.1 2019-01-13 19:06:07 +01:00
Otto Winter
145e7b00ee Bump version to v1.10.1 2019-01-13 19:04:57 +01:00
Otto Winter
07c80dfcda Pin platformio platforms (#335) 2019-01-13 19:04:54 +01:00
Otto Winter
0e52c9a778 Introduce wifi fast connect mode (#333) 2019-01-13 19:04:54 +01:00
Otto Winter
94bd179256 Fix show logs with MQTT and dashboard (#332)
Fixes #327
2019-01-13 19:04:54 +01:00
Otto Winter
11c38ca4e8 Fix AsyncTCP compilation on ESP32 with Arduino breaking change (#334) 2019-01-13 19:04:54 +01:00
Otto Winter
dc71d11a21 Fix ESP32 not decoding stacktrace on broken PC (#330) 2019-01-13 19:04:53 +01:00
Otto Winter
86130b2954 Pin platformio platforms (#335) 2019-01-13 19:03:07 +01:00
Otto Winter
c2787c1ce5 Introduce wifi fast connect mode (#333) 2019-01-13 18:33:41 +01:00
Otto Winter
e81338b4f5 Fix show logs with MQTT and dashboard (#332)
Fixes #327
2019-01-13 16:21:10 +01:00
Otto Winter
40ab2d5e5a Fix AsyncTCP compilation on ESP32 with Arduino breaking change (#334) 2019-01-13 16:20:28 +01:00
Otto Winter
7a703d10f4 Fix ESP32 not decoding stacktrace on broken PC (#330) 2019-01-13 09:56:29 +01:00
Otto Winter
e385c8435b Fix auto_uart 2019-01-10 10:56:25 +01:00
Otto Winter
4c5f908e3a Fix auto_uart 2019-01-10 10:56:07 +01:00
Otto Winter
13eca6012d Bump HassIO version to v1.10.0 2019-01-09 20:30:58 +01:00
Otto Winter
27e1294630 Bump HassIO version to v1.10.0 2019-01-09 20:30:35 +01:00
Otto Winter
cb3e3e024d Bump version to v1.10.0 2019-01-09 20:29:28 +01:00
Otto Winter
79bdec32b8 Merge branch 'rc' 2019-01-09 20:29:15 +01:00
Otto Winter
c2fb71c41f Fix Gitlab CI 2019-01-09 16:30:35 +01:00
Otto Winter
c153dba5bc Fix Gitlab CI 2019-01-09 16:29:21 +01:00
Otto Winter
fa2c2917c1 Bump beta version to v1.10.0b2 2019-01-09 16:16:19 +01:00
Otto Winter
7685b32ac0 Bump beta version to v1.10.0b2 2019-01-09 16:16:15 +01:00
Otto Winter
2a06f4dbf4 Bump version to v1.10.0b2 2019-01-09 16:14:47 +01:00
Otto Winter
49b618bb0c Fix interval trigger (#313) 2019-01-09 16:14:43 +01:00
escoand
20ec3900be use full space on small devices (#310)
* use full space on small devices

I often use my smartphone for quick actions and always was annoyed of this wasted space.

* hide card-image on small devices
2019-01-09 16:14:43 +01:00
Otto Winter
605be1a6ec OTA don't error when upgrading from no password to password mode (#309) 2019-01-09 16:14:43 +01:00
Otto Winter
33b67de32e Fix component.update action (#308)
Fixes https://github.com/OttoWinter/esphomeyaml/issues/286
2019-01-09 16:14:43 +01:00
Otto Winter
5a1e806d14 Fix interval trigger (#313) 2019-01-09 15:18:54 +01:00
escoand
cf73d777db use full space on small devices (#310)
* use full space on small devices

I often use my smartphone for quick actions and always was annoyed of this wasted space.

* hide card-image on small devices
2019-01-09 14:58:23 +01:00
Otto Winter
47231977d7 OTA don't error when upgrading from no password to password mode (#309) 2019-01-08 12:51:26 +01:00
Otto Winter
d29b280d82 Fix component.update action (#308)
Fixes https://github.com/OttoWinter/esphomeyaml/issues/286
2019-01-08 12:15:03 +01:00
Otto Winter
77514c0a06 Update config.json 2019-01-07 15:54:24 +01:00
Otto Winter
1ffedb291c Update beta config (#305)
* Update beta Hass.io add-on config

* Add README
2019-01-06 21:29:33 +01:00
Otto Winter
341a27e56b Update beta config (#305)
* Update beta Hass.io add-on config

* Add README
2019-01-06 21:26:39 +01:00
Otto Winter
8f251848ef Bump beta version to v1.10.0b1 2019-01-06 19:39:15 +01:00
Otto Winter
739b7bfc05 Bump beta version to v1.10.0b1 2019-01-06 19:39:12 +01:00
Otto Winter
4046a16d85 Merge branch 'dev' into rc 2019-01-06 19:38:23 +01:00
Otto Winter
01b49e8d59 Home Assistant platform tests 2019-01-06 19:03:34 +01:00
Otto Winter
52dbd35118 Add neopixelbus tests 2019-01-06 16:59:11 +01:00
Otto Winter
92439abeb4 Add tests, Fix stuff 2019-01-06 11:43:14 +01:00
Otto Winter
9bce35e335 Make tzlocal a requirement 2019-01-06 09:48:03 +01:00
Otto Winter
94cb7bf6bd Validate MQTT enabled for MQTT options 2019-01-06 09:47:47 +01:00
Otto Winter
cdf53cb3e8 Remove component that was never added 2019-01-06 09:47:28 +01:00
Otto Winter
30bd74cb6c Simplify validator 2019-01-06 09:47:14 +01:00
Otto Winter
e72ef9e061 Add ESP32 Ethernet Support (#301)
* Fix

* Build flags

* Fix
2019-01-05 20:47:33 +01:00
Otto Winter
3f4bba57f4 ULN2003 Support (#304) 2019-01-05 20:11:31 +01:00
sherbang
38c24fd100 Add support for MAX31855 sensor (#258)
* Add support for MAX31855 sensor

* Update for latest core changes

* Update max31855.py
2019-01-05 20:08:25 +01:00
Otto Winter
c6ed88579f Add neopixelbus component (#303) 2019-01-05 20:01:19 +01:00
Otto Winter
7f9462ebf3 Add APDS9960 Support (#300) 2019-01-05 19:52:01 +01:00
Otto Winter
4e44081fdf Add clean MQTT discovery option for native API (#302) 2019-01-05 19:51:01 +01:00
Otto Winter
3cd1c2d723 Fix Python 3 conversion errors
Fixes #299
2019-01-05 14:24:15 +01:00
Otto Winter
907be3025c Supply return_type for more lambdas 2019-01-03 20:39:54 +01:00
Otto Winter
815da05b29 Fix OTA Auth for Python 3 2019-01-03 19:54:36 +01:00
Otto Winter
a2083fc901 Add ACE searchbox 2019-01-03 19:54:21 +01:00
Otto Winter
06cb7497c8 Another fix for Python 3 2019-01-03 17:09:28 +01:00
Otto Winter
1e12cba176 Fixes for Python 3 Compatability (#297) 2019-01-03 16:05:33 +01:00
Vladimir Eremin
1d0c812e44 GPIO Switch Fix restore_mode validator (#296) 2019-01-02 18:24:47 +01:00
Otto Winter
4948ad95b0 Lint 2019-01-02 18:24:20 +01:00
Otto Winter
dd801636af Fix Interval Trigger 2019-01-02 16:49:26 +01:00
Otto Winter
a110911371 Commit forgotten local change
Fixes #295
2019-01-02 16:34:38 +01:00
Otto Winter
22fd4ec722 Make compatible with python 3 (#281)
* Make compatible with python 3

* Update travis

* Env

* Fix typo

* Fix install correct platformio

* Lint

* Fix build flags

* Lint

* Upgrade pylint

* Lint
2019-01-02 14:11:11 +01:00
Otto Winter
5db70bea3c Toggle Auto-Update Check With Environment Variable (#292) 2019-01-02 14:02:59 +01:00
Otto Winter
00ff99cc7d Addressable Lights (#294) 2019-01-02 13:04:27 +01:00
Otto Winter
a51eaa93b5 GPIO Switch Restore Mode (#287) 2019-01-02 12:30:55 +01:00
Otto Winter
eddaee3a80 Fix MQTT message trigger (#282)
Fixes https://github.com/OttoWinter/esphomelib/issues/335
2019-01-02 12:29:29 +01:00
Otto Winter
2b432ea829 Disable SPIFFS to save flash space (#288) 2019-01-02 12:29:15 +01:00
Otto Winter
09ab55b85d API Server Watchdog (#290) 2019-01-02 12:27:16 +01:00
Otto Winter
183e0f4d23 Fix ESP8266 verbose logging (#291) 2019-01-02 12:27:05 +01:00
Otto Winter
a8c17e5d05 Fix host network (#280)
* Fix Add-On host network mode

* Split up esphomeyaml tests

* Fix perms

* Fix

* Add esphomeyaml_version option

* Revert change to travis.yml
2019-01-02 12:21:26 +01:00
Otto Winter
2e65d6c02c Api fixes (#289)
* API Client Fixes

* Fix redirect stdout stderr
2019-01-02 12:15:13 +01:00
Otto Winter
07da8950c4 Lint 2019-01-02 12:02:28 +01:00
Otto Winter
c206846816 Better internal debugging 2018-12-30 12:46:08 +01:00
Otto Winter
6aac4191f8 Hash dashboard resources 2018-12-24 14:29:11 +01:00
Otto Winter
5a7b66e207 Fix some stuff 2018-12-24 14:15:54 +01:00
Otto Winter
da2821ab36 Add native ESPHome API (#265)
* Esphomeapi

* Updates

* Remove MQTT from wizard

* Add protobuf to requirements

* Fix

* API Client updates

* Dump config on API connect

* Old WiFi config migration

* Home Assistant state import

* Lint
2018-12-18 19:31:43 +01:00
Otto Winter
7556845079 Fix download binary button for firefox 2018-12-07 10:17:19 +01:00
Otto Winter
e646f02e27 Update Hass.io READMe 2018-12-05 22:42:40 +01:00
Otto Winter
39ead80df3 Fix Hass.io requirements script 2018-12-05 22:35:35 +01:00
Otto Winter
71e221f6ec Fix 2018-12-05 21:34:24 +01:00
Otto Winter
7c7032c59e [Huge] Util Refactor, Dashboard Improvements, Hass.io Auth API, Better Validation Errors, Conditions, Custom Platforms, Substitutions (#234)
* Implement custom sensor platform

* Update

* Ethernet

* Lint

* Fix

* Login page

* Rename cookie secret

* Update manifest

* Update cookie check logic

* Favicon

* Fix

* Favicon manifest

* Fix

* Fix

* Fix

* Use hostname

* Message

* Temporary commit for screenshot

* Automatic board selection

* Undo temporary commit

* Update esphomeyaml-edge

* In-dashboard editing and hosting files locally

* Update esphomeyaml-edge

* Better ANSI color escaping

* Message

* Lint

* Download Efficiency

* Fix gitlab

* Fix

* Rename extra_libraries to libraries

* Add example

* Update README.md

* Update README.md

* Update README.md

* HassIO -> Hass.io

* Updates

* Add update available notice

* Update

* Fix substitutions

* Better error message

* Re-do dashboard ANSI colors

* Only include FastLED if user says so

* Autoscroll logs

* Remove old checks

* Use safer RedirectText

* Improvements

* Fix

* Use enviornment variable

* Use http://hassio/host/info

* Fix conditions

* Update platformio versions

* Revert "Use enviornment variable"

This reverts commit 7f038eb5d2.

* Fix

* README update

* Temp

* Better invalid config messages

* Platformio debug

* Improve error messages

* Debug

* Remove debug

* Multi Conf

* Update

* Better paths

* Remove unused

* Fixes

* Lint

* lib_ignore

* Try fix platformio colors

* Fix dashboard scrolling

* Revert

* Lint

* Revert
2018-12-05 21:22:06 +01:00
Otto Winter
2b88c987da Fix GPIO input schema validator (#253) 2018-12-02 13:16:02 +01:00
Otto Winter
3d49ffa134 Time SNTP validate server format (#254) 2018-12-02 13:15:28 +01:00
Otto Winter
5ed987adcd Bump HassIO version to v1.9.3 2018-12-01 13:38:05 +01:00
Otto Winter
a463c59733 Bump version to v1.9.3 2018-12-01 13:37:28 +01:00
Otto Winter
1726c4237b CSE7766 update interval (#250)
* CSE7766 update interval

* PollingComponent
2018-12-01 13:37:25 +01:00
Otto Winter
f2f869db89 Bump HassIO version to v1.9.3 2018-12-01 13:23:16 +01:00
Otto Winter
e37e986276 CSE7766 update interval (#250)
* CSE7766 update interval

* PollingComponent
2018-12-01 13:19:34 +01:00
Otto Winter
f1241af91d Bump HassIO version to v1.9.2 2018-11-25 19:18:17 +01:00
Otto Winter
7ae6777fd6 Bump version to v1.9.2 2018-11-25 19:17:32 +01:00
Otto Winter
a169d37557 Fix fastled lambda light effect
Fixes #232
2018-11-25 19:05:39 +01:00
Otto Winter
5d5529ff15 Bump HassIO version to v1.9.2 2018-11-25 18:00:20 +01:00
Otto Winter
db90a7a334 Add Logo and Icon to HassIO add-on (#241) 2018-11-23 13:51:39 +01:00
Otto Winter
bb9c1faffa Improve one_of validator (#240) 2018-11-23 13:51:22 +01:00
Otto Winter
be21aa786d Bump HassIO version to v1.9.1 2018-11-19 23:05:18 +01:00
Otto Winter
9a881100e6 Bump version to v1.9.1 2018-11-19 23:04:56 +01:00
Otto Winter
c2f88776c7 Fix SNTP servers option (#237)
* Fix SNTP servers option

* Lint
2018-11-19 23:04:45 +01:00
Otto Winter
b4c4dc8cfb Bump HassIO version to v1.9.1 2018-11-19 23:01:27 +01:00
Otto Winter
c3b2ab0085 Fix SNTP servers option (#237)
* Fix SNTP servers option

* Lint
2018-11-19 22:49:06 +01:00
Otto Winter
846fcb8ccd Bump HassIO version to v1.9.0 2018-11-15 12:07:23 +01:00
Otto Winter
f087e313d4 Bump HassIO version to v1.9.0 2018-11-15 12:07:20 +01:00
Otto Winter
44495c919c Bump version to v1.9.0 2018-11-15 12:05:25 +01:00
Otto Winter
d4ce7699d4 Merge branch 'rc' 2018-11-15 12:05:18 +01:00
Otto Winter
f88cf99b67 Cleanup git auto-update 2018-11-15 10:13:32 +01:00
Otto Winter
f394968bd0 Fix fastled lambda light effect
Fixes #232
2018-11-15 10:13:21 +01:00
Otto Winter
318bb8b254 Bump beta version to v1.9.0b6 2018-11-13 19:10:32 +01:00
Otto Winter
70def85ba1 Bump beta version to v1.9.0b6 2018-11-13 19:10:28 +01:00
Otto Winter
1ad65516cf Bump version to v1.9.0b6 2018-11-13 19:06:32 +01:00
Otto Winter
d4c7e6c634 Merge branch 'dev' into rc 2018-11-13 19:06:26 +01:00
Otto Winter
9b07bb6608 Fix my9231 IDs not being resolved 2018-11-13 19:00:36 +01:00
Otto Winter
f368255739 Update MY9231 2018-11-13 16:53:46 +01:00
puuu
083c2fce05 Add MY9231 support (#227) 2018-11-13 16:51:30 +01:00
Otto Winter
c99d4e2815 Deep Sleep Wake Up From Multiple Pins (#230) 2018-11-13 15:36:49 +01:00
Otto Winter
39457f7b8c Enable nodelay for phase 1 of OTAv2 2018-11-13 15:31:14 +01:00
Otto Winter
01aaf14078 Add frequency option to ESP8266 PWM 2018-11-13 12:02:19 +01:00
Otto Winter
9a939d2d27 Bump beta version to v1.9.0b5 2018-11-12 23:44:57 +01:00
Otto Winter
7f91141df2 Bump beta version to v1.9.0b5 2018-11-12 23:44:55 +01:00
Otto Winter
06eeed9ee9 Bump version to v1.9.0b5 2018-11-12 23:43:03 +01:00
Otto Winter
5655b5fe10 Merge branch 'dev' into rc 2018-11-12 23:43:00 +01:00
Otto Winter
15331edb78 Let esphomeyaml know about class inheritance (#229)
* Allow overriding setup priority

* Add inheritance tree

* Global variables

* Tests and better validation

* Fix

* Lint
2018-11-12 23:30:31 +01:00
Otto Winter
4f375757a5 Better validation error messages 2018-11-12 21:00:17 +01:00
Otto Winter
0dec7cfbf8 Lint 2018-11-10 15:39:25 +01:00
Otto Winter
f51d301d53 Bump beta version to v1.9.0b4 2018-11-10 15:33:03 +01:00
Otto Winter
c3b3ba4923 Bump beta version to v1.9.0b4 2018-11-10 15:33:01 +01:00
Otto Winter
30e7797577 Bump version to v1.9.0b4 2018-11-10 15:31:20 +01:00
Otto Winter
0e5cabadc1 Merge branch 'dev' into rc 2018-11-10 15:31:16 +01:00
Otto Winter
58f4fa53d0 Make Switch not inherit from Component 2018-11-10 14:07:11 +01:00
Otto Winter
04dc848620 Improve OTAv2 error messages 2018-11-10 11:45:38 +01:00
Otto Winter
8203b8fcd3 Add total daily energy sensor (#220)
* Add total daily energy sensor

* Add test
2018-11-09 20:27:19 +01:00
Otto Winter
0ad61f4a95 Add binary sensor multi click trigger (#226) 2018-11-09 20:18:04 +01:00
Otto Winter
9fd4076ab8 Revert Add power on value to GPIO switch (#223)
* Revert Add power on value to GPIO switch

* Fix tests

* Fix
2018-11-09 20:08:35 +01:00
Otto Winter
a9e799cb06 Clean up time API (#221) 2018-11-09 20:08:16 +01:00
Otto Winter
3ec931ffa4 Add restore state option to template switch (#222)
* Add restore state option to template switch

* Add test
2018-11-09 20:05:50 +01:00
Otto Winter
e3094d9689 OTA Send back acknowledgement 2018-11-09 14:27:12 +01:00
Otto Winter
3594779401 Better typing to components (#225)
* Add better typing

* Fix
2018-11-08 16:01:07 +01:00
Otto Winter
94978d0063 Fix HLW8012 Voltage Divider option not being added to source (#224) 2018-11-07 23:27:06 +01:00
Otto Winter
415e12309b Improve OTA v2 error and log messages 2018-11-07 22:41:54 +01:00
Otto Winter
aa5f887ff3 Fix Gitlab CI 2018-11-04 00:10:44 +01:00
Otto Winter
28561ea6a4 Merge branch 'dev' into rc 2018-11-03 17:52:22 +01:00
Otto Winter
3b3ff4fea9 Bump beta version to v1.9.0b3 2018-11-03 17:47:16 +01:00
Otto Winter
6b8125f5f2 Bump version to v1.9.0b3 2018-11-03 17:47:12 +01:00
Otto Winter
ab43390983 Merge branch 'dev' into rc 2018-11-03 17:47:06 +01:00
Otto Winter
611592170b Fix esphomeyaml-edge 2018-11-03 17:35:05 +01:00
Otto Winter
8a58ff91c3 Update Gitlab Build Script (#215) 2018-11-03 16:24:26 +01:00
Otto Winter
27b86d89b0 Add generate home assistant config command (#208)
* Add generate home assistant config command

* Lint

* Lint
2018-11-03 14:08:31 +01:00
Otto Winter
36da3b85d5 Auto-Decode stacktraces (#214)
* Auto-Decode stacktraces

* Fix Gitlab CI
2018-11-03 14:06:14 +01:00
Otto Winter
d7d3a4aa36 Bump beta version to v1.10.0-dev 2018-11-03 12:09:22 +01:00
Otto Winter
4e4ffc3a24 Bump beta version to 1.9.0b2 2018-10-31 17:47:34 +01:00
Otto Winter
4dce7fa103 Bump version to 1.9.0b2 2018-10-31 16:35:45 +01:00
Otto Winter
467ef9902f Merge branch 'dev' into rc 2018-10-31 16:34:36 +01:00
Otto Winter
486174073b Update .gitlab-ci.yml 2018-10-31 16:34:03 +01:00
Otto Winter
e9d9de448e Fix output actions 2018-10-31 16:27:30 +01:00
Otto Winter
08c16020c6 Fix output platforms 2018-10-29 22:46:00 +01:00
Otto Winter
0ade9baf65 Fix automation validation 2018-10-27 14:10:37 +02:00
Otto Winter
2fab7e73b9 Add send_first_at option to sliding window sensor filter (#207)
* Add send_first_at option to sliding window sensor filter

* Lint
2018-10-26 22:59:03 +02:00
Otto Winter
af4e2bf61d Add Stepper Motor Support (#206)
* Add stepper support

* Fix output set_level

* Lint
2018-10-26 22:57:03 +02:00
Otto Winter
6a2e9a8503 Add beta add-on 2018-10-20 19:15:49 +02:00
Otto Winter
74fefea5bb Fix gitlab 2018-10-20 19:14:27 +02:00
Otto Winter
21c22fe04a Bump version to 1.10.0-dev 2018-10-20 18:32:06 +02:00
Otto Winter
70206df8b5 Fix gitlab 2018-10-20 18:26:44 +02:00
Otto Winter
15732ca465 Bump version to 1.9.0b1 2018-10-20 18:24:02 +02:00
Otto Winter
8e0f4f93d4 Fix HassIO add-on archs 2018-10-20 18:20:42 +02:00
Otto Winter
361baea17f Add beta builds 2018-10-20 18:20:21 +02:00
Otto Winter
8bbfbc4cc1 Add logger.log action (#198)
* Add logger.log Action

* Simple schema

* Validate printf

* Improve error message

* Undo unfix tests :)
2018-10-20 15:19:59 +02:00
Otto Winter
8c5d12df51 Fix some typos (#202) 2018-10-20 15:18:12 +02:00
Otto Winter
25c66ed8ca Improve API naming convention consistency (#197)
* Improve API naming convention consistency

* Fix
2018-10-20 15:16:58 +02:00
Otto Winter
7a55521807 Fix rebase error 2018-10-20 15:08:55 +02:00
Otto Winter
d04de7baeb Fix value range trigger 😑 (#201) 2018-10-20 14:40:46 +02:00
Otto Winter
4aeb756388 Fix triggers being interpreted as a sequence of automations (#199) 2018-10-20 13:15:09 +02:00
Otto Winter
92b6ed4180 Add FastLED color correction option (#200)
* Add FastLED color correction option

* Add test
2018-10-20 13:14:02 +02:00
Otto Winter
9efd9f4fe8 Add PMSX003 Particulate Matter Sensor (#192) 2018-10-20 12:58:52 +02:00
Otto Winter
34fc2147a4 Add update component action and scripts (#196)
* Add update component action

* Add script component
2018-10-20 12:58:02 +02:00
Otto Winter
629f2b128e Add MQTT publish JSON action and subscribe JSON trigger (#193)
* Add MQTT publish JSON action and subscribe JSON trigger

* Lint
2018-10-20 12:41:00 +02:00
Otto Winter
81bc400340 Add PN532 On Tag Trigger (#189)
* Add PN532 On Tag Trigger

* Lint

* Fix 😶

* Fix
2018-10-17 21:29:44 +02:00
Otto Winter
75628b96a1 Add Text Sensors (#166)
* Text Sensors

* Add version text sensor

* Fix

* Fixes

* Add tests

* Add template text sensor

* Lint

* Fix test
2018-10-17 21:24:02 +02:00
Otto Winter
b1f7ed4fdc Add CSE776 for Sonoff Pow R2 (#190) 2018-10-17 21:14:31 +02:00
Otto Winter
b8d7185d99 Unify Xiaomi implementations (#188) 2018-10-17 20:56:55 +02:00
Otto Winter
2d20a1c0fb Decentralize Automation Generator Code (#182)
* Decentralize Automation Generator Code

* Lint
2018-10-16 23:16:06 +02:00
Otto Winter
820067ae5a GPIO Switch Power On Value v2 (#183) 2018-10-16 22:58:26 +02:00
Otto Winter
db8313e0d5 Fix config dump time output (#184) 2018-10-16 20:45:24 +02:00
Otto Winter
27a77c685d Rework OTA to be more stable (#177)
* Rework OTA to be more stable

* Lint
2018-10-14 19:14:28 +02:00
Otto Winter
e34365dc7c Add clean build files command and auto-clean on version change (#181)
* Add clean build files command and auto-clean on version change

* Update __main__.py
2018-10-14 18:52:21 +02:00
Otto Winter
8d395e5338 MQTT different log level (#167)
* Add option to have different log level over MQTT

* Add Test

* Lint
2018-10-14 18:46:17 +02:00
Otto Winter
6f54afec00 Add MQTT Subscribe sensor (#175) 2018-10-14 18:45:13 +02:00
Otto Winter
6a24145be6 Fix Wifi power_save_mode option (#178)
* Fix WIFI power_save_mode option

* Add Test
2018-10-13 21:17:19 +02:00
escoand
4a2cdbf31c Add Samsung IR protocol (#176)
* add Samsung ir protocol

* fix pylint

* add test

* add transmitter
2018-10-13 19:21:06 +02:00
Otto Winter
1d75ed1ff4 Add use_build_flags removal notice (#173) 2018-10-12 12:12:07 +02:00
Otto Winter
76b1c6f47b Fix readme broken link (#174) 2018-10-12 12:05:48 +02:00
Otto Winter
06371c9e2d Create issue templates (#171)
* Create issue templates

* Update bug_report.md
2018-10-12 11:27:14 +02:00
Otto Winter
a9c130dd50 Add Code of Conduct (Contributor Covenant) (#168) 2018-10-12 11:26:26 +02:00
Otto Winter
1f82c1a483 Create CONTRIBUTING.md (#169) 2018-10-12 11:26:05 +02:00
Otto Winter
cb28429231 Create Pull Request Template (#172)
* Create PULL_REQUEST_TEMPLATE.md

* Update PULL_REQUEST_TEMPLATE.md

* Rename PULL_REQUEST_TEMPLATE.md to .github/PULL_REQUEST_TEMPLATE.md
2018-10-12 11:25:43 +02:00
Otto Winter
f2cd2ec178 Fix raw remote receiver (#158) 2018-10-12 09:49:10 +02:00
Otto Winter
37360bb797 Log esphomelib version and compilation time on boot (#159) 2018-10-12 09:48:55 +02:00
JonnyaiR
6c1dc0f2b3 Add a link to Home Assistant in README (#152)
As "Home Assistant" could be interpreted as a generic term for automating a home, i've added links to the Home-Assistant website :-)
2018-10-08 11:00:02 +02:00
Otto Winter
f7671f0c90 Bump version to 1.8.2 2018-10-07 17:01:15 +02:00
Otto Winter
639b97ccb2 WiFi: Add power save mode option (#150)
* WiFi: Add power save mode option

* Lint
2018-10-07 16:52:14 +02:00
Otto Winter
0374b3a0b3 Fix component loader value error (#149)
* Print better error message when loader fails with ValueError

* Improve

* Improve
2018-10-07 14:38:22 +02:00
Otto Winter
7ce753b76f Docker default to starting dashboard (#143) 2018-10-04 19:01:57 +02:00
Otto Winter
05a1089ed2 Bump platformio-espressif32 to 1.4.0 (#142) 2018-10-04 19:01:24 +02:00
Otto Winter
71947bb6ac Fix using unicode in lambdas (#141)
https://github.com/OttoWinter/esphomeyaml/issues/128#issuecomment-425777989
2018-10-04 19:01:13 +02:00
Otto Winter
ef54e33b70 Add clean MQTT button to dashboard (#139) 2018-10-04 19:01:02 +02:00
Otto Winter
12f20fc3cf Fix serial monitor opening when logger disabled (#138)
Fixes #137
2018-10-04 19:00:51 +02:00
Otto Winter
ffdcddc18e Add SSD1306 64x48 display (#136) 2018-09-29 14:50:14 +02:00
Otto Winter
85d70eb5a0 Auto-Update esphomelib dev version (#134)
* Auto-Update esphomelib dev version

* Lint
2018-09-29 14:50:04 +02:00
Otto Winter
ffb793177a Enable Travis Tests (#133) 2018-09-28 19:34:28 +02:00
Otto Winter
d3f2fab88a Fix SSD1306 lambda (#132)
As per #128
2018-09-28 18:17:22 +02:00
Otto Winter
0fa52d0ce6 Fix MQTT discovery enabled when discovery_retain in config (#131) 2018-09-27 16:34:11 +02:00
Otto Winter
cf264a2743 Fix binary sensor heartbeat not working (#130) 2018-09-27 16:34:02 +02:00
Otto Winter
433b605bef Bump version to 1.8.1 2018-09-26 19:00:41 +02:00
Otto Winter
6e60c6493a Use cache for HassIO add-on images 2018-09-26 18:59:39 +02:00
Otto Winter
4e63bc96d5 Fix main docker image missing platformio 2018-09-26 18:39:55 +02:00
Otto Winter
ce4b339d16 Split up BLE tests into new file 2018-09-26 18:39:41 +02:00
Otto Winter
ab6d293d0d Fix docker installs using old platformio version (#125)
* Fix min platformio version and update requirements

* Remove unnecessary requirements from travis
2018-09-27 01:14:51 +09:00
Otto Winter
5e5137960d Limit upload speed to 115200 (#122)
* Limit upload speed to 115200

* Lint
2018-09-26 01:27:47 +09:00
Otto Winter
8dba37846b Bump version to 1.8.0 2018-09-25 13:43:45 +02:00
Otto Winter
5cd82d7c25 Try fix docker build 2018-09-25 13:26:01 +02:00
Otto Winter
bc8354bad5 Lint 2018-09-25 10:36:12 +02:00
Otto Winter
2abbe1bca3 Updates 2018-09-25 10:30:45 +02:00
Otto Winter
74c70509c2 Fix docker build 2018-09-25 10:30:45 +02:00
Otto Winter
1576e1847e Lint 2018-09-25 10:30:45 +02:00
John Erik Halse
9ea9b4b102 Use tls for connecting to mqtt broker when fingerprints is set (#118) 2018-09-24 22:27:38 +09:00
Adriaan Peeters
490743c26e Add ‘only-generate’ parameter to generate command to only generate the C++ code (#84)
* Introduce parser_compile variable

* Add ‘only-generate’ parameter to the compile command which only generates the C++ code
2018-09-19 21:51:39 +09:00
Otto Winter
1c7bddd005 Last Touches 2018-08-25 22:18:22 +02:00
Otto Winter
5c39f73fda Further changes 2018-08-24 22:44:15 +02:00
Otto Winter
2fc78a1b33 Fixes for release-candidate 2018-08-22 22:04:56 +02:00
Otto Winter
03249780fd Displays 2018-08-18 21:40:59 +02:00
Otto Winter
5170a7cdf4 Smallish Update 2018-08-13 19:11:33 +02:00
Otto Winter
5680de79a9 Create LICENSE 2018-07-28 16:33:01 +02:00
Otto Winter
61bd2a3a44 Revert "Revert "Add password to esphomeyaml non-dev config""
This reverts commit 7802c63f5a.
2018-06-14 10:08:27 +02:00
Otto Winter
7802c63f5a Revert "Add password to esphomeyaml non-dev config"
This reverts commit 94bef2a5a4.
2018-06-14 09:39:18 +02:00
Otto Winter
94bef2a5a4 Add password to esphomeyaml non-dev config 2018-06-14 09:29:53 +02:00
Otto Winter
12c4b0788c Small fixes 2018-06-14 00:14:01 +02:00
Otto Winter
b13cc3a4a5 Fix lint 2018-06-13 23:01:50 +02:00
Otto Winter
c5d9bc5452 Bump version to 1.7.0 2018-06-13 22:56:49 +02:00
Otto Winter
6c6d21a7ab Allow simpler automation syntax 2018-06-13 21:27:58 +02:00
Otto Winter
9bb06782b2 Automate HassIO builds using Gitlab CI (#52)
* Add Gitlab CI

* Fix aarch64 edge build
2018-06-13 11:13:53 +02:00
Otto Winter
a827b51887 Add ESP32 BLE Beacon 2018-06-12 21:18:04 +02:00
Otto Winter
2c30d80490 Fix multi wifi validation 2018-06-11 16:13:46 +02:00
Otto Winter
e0acdc3ae0 Revert Multi Wifi 2018-06-11 15:22:03 +02:00
Otto Winter
36a3f96011 Fix RF receiver dumpers 2018-06-11 15:21:38 +02:00
Otto Winter
6ae8e3495f Add build_path option. Fixes#49 2018-06-11 10:12:44 +02:00
Otto Winter
6aa449115f Add internal modifier. Fixes ottowinter/esphomelib#77 2018-06-11 10:01:54 +02:00
Otto Winter
0be29d27d5 Fixes esphomelib#72 2018-06-08 20:33:14 +02:00
Otto Winter
68fa7489a2 Improve time config validation 2018-06-08 20:33:14 +02:00
Johan Bloemberg
d7699c93d6 Fix deprecation of board_flash_mode parameter (#41)
* Fix deprecation of board_flash_mode parameter

Fixes `Warning! `board_flash_mode` option is deprecated and will be removed in the next release! Please use `board_build.flash_mode` instead.`

* Don't break flashing for older installations.
2018-06-08 11:34:06 +02:00
Johan Bloemberg
a04438e924 Make sure logs after upload works when using explicit OTA. (#42) 2018-06-08 10:41:01 +02:00
Otto Winter
e063f2aaea Fix hmac with non-ASCII passwords 2018-06-07 21:52:41 +02:00
Otto Winter
7b630bfb8b Fix HassIO edge config 2018-06-07 21:22:11 +02:00
Otto Winter
ec3366cce0 Dashboard authentication 2018-06-07 20:47:06 +02:00
Johan Bloemberg
135117714b Support specifying hostname/ip as --upload-port (#36) 2018-06-07 20:36:00 +02:00
Brandon Davidson
91e6304505 Add WiFi Signal Strength sensor (#35) 2018-06-07 19:29:18 +02:00
Otto Winter
361a9da868 on_boot and on_shutdown triggers 2018-06-07 17:06:50 +02:00
Otto Winter
31d7656c07 Lint 2018-06-07 17:04:33 +02:00
Otto Winter
d1a7751dc9 SHT3X-D Remove Accuracy Option 2018-06-07 11:27:04 +02:00
Otto Winter
e47bcb9abb Bump version to 1.6.2 2018-06-06 21:01:20 +02:00
Otto Winter
30b94f06b3 Add verbose mode for espota 2018-06-06 11:12:42 +02:00
Otto Winter
19dfdb77eb Fix MQTT logs 2018-06-06 11:12:29 +02:00
Otto Winter
b6e7ad3589 Fix OTA port not being set 2018-06-06 11:12:14 +02:00
Otto Winter
1267680379 Improve error messages 2018-06-06 10:27:59 +02:00
Otto Winter
b5f6b32fad Fix dallas resolution 2018-06-06 10:27:50 +02:00
Otto Winter
7068808d4f Fix optional returns from templates 2018-06-06 09:48:59 +02:00
Otto Winter
c82a44d541 Fix ESP32 touch sleep duration conversion 2018-06-06 09:30:14 +02:00
Otto Winter
e650473682 Fix ESP32 touch ID issue 2018-06-06 09:26:56 +02:00
Otto Winter
2ee0d4242d Make Automation output readable again 2018-06-06 09:18:39 +02:00
Otto Winter
b2aecf29dc Fix ESP32 BLE 2018-06-06 09:18:21 +02:00
Otto Winter
081c2f4e07 Fix SHT3xD with manual update interval 2018-06-06 09:06:14 +02:00
Otto Winter
0c77228421 Improve wrong pin error message 2018-06-06 08:55:53 +02:00
Otto Winter
8a509c6551 Fix MQTT publish 2018-06-06 08:35:31 +02:00
Otto Winter
d249820bcd Fix MQTT availability option 2018-06-06 08:35:23 +02:00
Otto Winter
f39cf52eae Fix WiFi domain option 2018-06-06 08:13:05 +02:00
Otto Winter
63fb252b46 Fix web server CSS/JS URL 2018-06-06 08:12:51 +02:00
Otto Winter
2f98adca49 Fix OrFilter 2018-06-06 08:12:39 +02:00
Otto Winter
084fc00517 Fix MQTT messages 2018-06-06 08:12:30 +02:00
Otto Winter
abeac23abd Fix i2c frequency 2018-06-06 08:12:17 +02:00
Otto Winter
7264e3a4bf Fix ultrasonic typo Fixes #62 2018-06-06 07:09:03 +02:00
Otto Winter
16eeb3af31 Attempt to solve UnicodeEncodeError in HassIO when running on RPi 2018-06-05 23:19:46 +02:00
Otto Winter
b799d2df7f Adjust serial port fetch interval 2018-06-05 23:19:28 +02:00
Otto Winter
11d55fec4f Better OSError handling for YAML parser 2018-06-05 23:18:56 +02:00
Otto Winter
923a5990c7 Fix erroneous validation. Fixes OttoWinter/esphomelib#59 2018-06-05 22:32:34 +02:00
Otto Winter
a0a615c335 Deep Sleep wakeup pin mode 2018-06-05 22:31:39 +02:00
Otto Winter
e9ced0cc3f Fix lint 2018-06-04 20:47:29 +02:00
Otto Winter
1a92381994 Update Dockerfile 2018-06-04 20:47:23 +02:00
Otto Winter
a07a7a87a2 Fix filter out nan filter 2018-06-04 20:47:15 +02:00
Otto Winter
6a823f5777 Bump version to 1.6.1 2018-06-03 23:45:57 +02:00
Otto Winter
419c5afe27 Update Dockerfiles 2018-06-03 23:45:29 +02:00
Otto Winter
17798dee1e HassIO add-on pre-built images 2018-06-03 19:22:25 +02:00
Otto Winter
ee2c53585f Add Sonoff S20 example 2018-06-03 13:00:40 +02:00
Otto Winter
2f05acfa5a Validate configuration button 2018-06-03 12:16:43 +02:00
Otto Winter
f4d393a59e Fix dashboard upload port selection 2018-06-03 11:18:53 +02:00
Otto Winter
967aa53bad add_job 2018-06-03 07:11:11 +02:00
Otto Winter
4f3f460105 ESP32 Hall Sensor 2018-06-02 23:25:13 +02:00
Otto Winter
6e85a741ae New coroutine-based task execution 2018-06-02 22:22:20 +02:00
Otto Winter
2db45898e2 Sonoff 4CH example with automation 2018-06-02 15:41:11 +02:00
Otto Winter
eb62599a98 ADC VCC support Fixes #26 2018-06-02 13:43:49 +02:00
Otto Winter
9f0737e5b9 Fix pylint... Again... 2018-06-01 23:01:31 +02:00
Otto Winter
7a393c1a3a Fix template/switch cover example. Fixes #27 2018-06-01 22:58:23 +02:00
Otto Winter
4fa7bc196a Warn about tornado. Fixes #24 2018-06-01 22:49:14 +02:00
Brandon Davidson
65d0dd47f3 Fix #27 - invalid code gen for Template Cover (#29) 2018-06-01 22:48:07 +02:00
Otto Winter
d88634b196 Bump version to 1.6.0 2018-06-01 18:48:27 +02:00
Otto Winter
976627eb38 HassIO add-on 2018-06-01 18:45:23 +02:00
Otto Winter
5b995c0692 Updates 2018-06-01 18:06:18 +02:00
Otto Winter
2d4b475951 Fix cover automation 2018-05-27 15:00:56 +02:00
Otto Winter
93d962dd43 HassIO -> dashboard 2018-05-27 14:15:24 +02:00
Otto Winter
2e7d8540fb Update dev library URI 2018-05-21 20:55:39 +02:00
Otto Winter
677fe8bacf More Actions 2018-05-21 17:01:47 +02:00
Otto Winter
94d7ac4ef0 HassIO add-on (#18)
* HassIO Beginnings

* Updates

* Fix pylint errors

* Fix pylint error
2018-05-21 16:40:22 +02:00
Jimmy Hedman
ebb6d0d464 Add domain paramteter to wifi. (#16)
* Add domain paramteter to wifi.

- To be able to do OTA updates on networks that doesn't use .local as
  local domain parameter domain is added to wifi section. It's currently
  only used for OTA.

* Centralised default parameter for domain.

* Added input validation for domainname.
2018-05-21 15:47:30 +02:00
Jimmy Hedman
e8fe653140 Fix flake8 warnings. (#17) 2018-05-21 15:07:17 +02:00
Otto Winter
374ea7044c Automation API & Cleanup 2018-05-20 12:41:52 +02:00
Otto Winter
061798839d Bump version to 1.5.3 2018-05-18 09:45:28 +02:00
Otto Winter
b9b09a1763 Ultrasonic sensor echo must be internal 2018-05-18 09:44:41 +02:00
Otto Winter
61b3ead2df More sensor filters 2018-05-18 09:44:25 +02:00
Otto Winter
48e42cf478 FastLED power supply 2018-05-18 09:44:03 +02:00
Otto Winter
1a9ff55a61 Improve PCF8574 validation 2018-05-17 21:56:50 +02:00
Otto Winter
e04285581c Rotary Encoders 2018-05-17 21:35:39 +02:00
Otto Winter
9af30061cb More filters 2018-05-17 21:31:39 +02:00
Otto Winter
19929fafa5 Rotary Encoder 2018-05-17 19:57:55 +02:00
Otto Winter
3a9febaf85 Generic Switch inversion support fixes #14 2018-05-17 17:20:43 +02:00
Otto Winter
ebb5991889 DHT12 Support 2018-05-17 17:19:16 +02:00
Otto Winter
f18f8444c7 Bump version to 1.5.2 2018-05-16 23:05:35 +02:00
Otto Winter
10607f2a51 Fix GPIO expression issue with no inverted set 2018-05-16 23:04:33 +02:00
Otto Winter
d3ac5bfb27 Bump version to 1.5.1 2018-05-16 22:45:06 +02:00
Otto Winter
4b9bb2b731 Initial Sonoff support 2018-05-16 19:45:33 +02:00
Otto Winter
8639eb1b27 Fix PCF8574 inverted KeyError #12 2018-05-15 11:22:55 +02:00
Otto Winter
9979ee6ddf Fix C++ String escaping fixes #11 2018-05-15 11:09:27 +02:00
Otto Winter
ee502a7aaa Bump version to 1.5.0 2018-05-14 22:10:17 +02:00
Otto Winter
dc516f7537 Fixes 2018-05-14 21:13:51 +02:00
Otto Winter
44f2b582b5 FastLED fixes 2018-05-14 17:34:43 +02:00
Otto Winter
262855ff62 Preparations for 1.5.0 2018-05-14 11:50:56 +02:00
Otto Winter
eec163644d Bump version to 1.4.0 2018-05-06 18:23:23 +02:00
Otto Winter
e65a4d50e5 Fix time config validation 2018-05-06 17:40:37 +02:00
Otto Winter
a88aad2179 Fix generic output 2018-05-06 17:40:27 +02:00
Otto Winter
49736c8c6d Update for 1.4.0 2018-05-06 15:56:12 +02:00
Otto Winter
7915e420f4 Fix Wemos D1 Mini Pin Numbering (fixes #9) 2018-04-24 21:52:59 +02:00
Otto Winter
595aa5e92d Bump version to 1.3.0 2018-04-18 19:33:53 +02:00
Otto Winter
ef1aa16627 Fix build 2018-04-18 18:44:37 +02:00
Otto Winter
3540b3fbb0 Web server (#7)
* Web Server

* Preparations for 1.3

* Fixes

* Fix Lint
2018-04-18 18:43:13 +02:00
Jimmy Hedman
ac5ab33975 Cleaned some low hanging pyling warnings. (#6) 2018-04-12 12:56:03 +02:00
Jimmy Hedman
633d20d023 Remove sleeps. (#5)
* Remove sleeps.

- the sleeps is not needed.

* Make sleep depending on environment variable.

- set QUICKWIZARD to true to disable sleeps.

* Changed env-name and made it work without env.

- It only worked when environment variable was defined. Now it works
  with variable unset, which should be the normal case.
- Added ESPHOMEYAML_ as prefix so it's ESPHOMEYAML_QUICKWIZARD.
2018-04-11 22:51:56 +02:00
Otto Winter
9e5548324b Fix lint error 2018-04-11 18:29:21 +02:00
Otto Winter
c31c5c4041 Bump version to 1.2.2 2018-04-10 20:43:19 +02:00
Otto Winter
34605f19ee Secret and Include directives in confg (#4) 2018-04-10 20:18:02 +02:00
Otto Winter
58e1b8454d Handle multiple serial ports better 2018-04-10 20:17:20 +02:00
Otto Winter
0ab63dc4d4 Enable Travis Linting (#3)
* Flake8 Travis Job

* Fix flake8 warnings

* Fix pylint errors

* Fix travis file
2018-04-10 17:17:46 +02:00
Jimmy Hedman
51c856e65e It now complies with flake8 --ignore=E501,W291 (#1)
- Not changning long lines or lines ending with space.
2018-04-10 16:21:32 +02:00
301 changed files with 27583 additions and 4706 deletions

View File

@@ -106,3 +106,5 @@ venv.bak/
config/
examples/
Dockerfile
.git/
tests/build/

14
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,14 @@
## Description:
**Related issue (if applicable):** fixes <link to issue>
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>
**Pull request in [esphome-core](https://github.com/esphome/esphome-core) with C++ framework changes (if applicable):** esphome/esphome-core#<esphome-core PR number goes here>
## 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 [esphomedocs](https://github.com/OttoWinter/esphomedocs).

7
.github/issue-close-app.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
comment: >-
https://github.com/esphome/esphome/issues/430
issueConfigs:
- content:
- "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
caseInsensitive: false

2
.gitignore vendored
View File

@@ -104,3 +104,5 @@ venv.bak/
.mypy_cache/
config/
tests/build/
tests/.esphome/

347
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,347 @@
---
# Based on https://gitlab.com/hassio-addons/addon-node-red/blob/master/.gitlab-ci.yml
variables:
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://docker:2375/
stages:
- lint
- test
- deploy
.lint: &lint
image: esphome/esphome-base-amd64
stage: lint
before_script:
- pip install -e .
- pip install flake8==3.6.0 pylint==1.9.4 pillow
tags:
- docker
.test: &test
image: esphome/esphome-base-amd64
stage: test
before_script:
- pip install -e .
tags:
- docker
variables:
TZ: UTC
.docker-base: &docker-base
image: esphome/esphome-base-builder
before_script:
- docker info
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
script:
- docker run --rm --privileged hassioaddons/qemu-user-static:latest
- TAG="${CI_COMMIT_TAG#v}"
- TAG="${TAG:-${CI_COMMIT_SHA:0:7}}"
- echo "Tag ${TAG}"
- |
if [[ "${IS_HASSIO}" == "YES" ]]; then
BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.3.0
BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH}
DOCKERFILE=docker/Dockerfile.hassio
else
BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.3.0
if [[ "${BUILD_ARCH}" == "amd64" ]]; then
BUILD_TO=esphome/esphome
else
BUILD_TO=esphome/esphome-${BUILD_ARCH}
fi
DOCKERFILE=docker/Dockerfile
fi
- |
docker build \
--build-arg "BUILD_FROM=${BUILD_FROM}" \
--build-arg "BUILD_VERSION=${TAG}" \
--tag "${BUILD_TO}:${TAG}" \
--file "${DOCKERFILE}" \
.
- |
if [[ "${RELEASE}" = "YES" ]]; then
echo "Pushing to ${BUILD_TO}:${TAG}"
docker push "${BUILD_TO}:${TAG}"
fi
- |
if [[ "${LATEST}" = "YES" ]]; then
echo "Pushing to :latest"
docker tag ${BUILD_TO}:${TAG} ${BUILD_TO}:latest
docker push ${BUILD_TO}:latest
fi
- |
if [[ "${BETA}" = "YES" ]]; then
echo "Pushing to :beta"
docker tag \
${BUILD_TO}:${TAG} \
${BUILD_TO}:beta
docker push ${BUILD_TO}:beta
fi
- |
if [[ "${DEV}" = "YES" ]]; then
echo "Pushing to :dev"
docker tag \
${BUILD_TO}:${TAG} \
${BUILD_TO}:dev
docker push ${BUILD_TO}:dev
fi
services:
- docker:dind
tags:
- docker
stage: deploy
flake8:
<<: *lint
script:
- flake8 esphome
pylint:
<<: *lint
script:
- pylint esphome
test1:
<<: *test
script:
- esphome tests/test1.yaml compile
test2:
<<: *test
script:
- esphome tests/test2.yaml compile
test3:
<<: *test
script:
- esphome tests/test3.yaml compile
.deploy-pypi: &deploy-pypi
stage: deploy
image: python:2.7
before_script:
- pip install -e .
- pip install twine
script:
- python setup.py sdist
- twine upload dist/*
tags:
- docker
deploy-release:pypi:
<<: *deploy-pypi
only:
- /^v\d+\.\d+\.\d+$/
except:
- /^(?!master).+@/
deploy-beta:pypi:
<<: *deploy-pypi
only:
- /^v\d+\.\d+\.\d+b\d+$/
except:
- /^(?!rc).+@/
.latest: &latest
<<: *docker-base
only:
- /^v([0-9\.]+)$/
except:
- branches
.latest-vars: &latest-vars
RELEASE: YES
LATEST: YES
# Also push to beta tag
BETA: YES
.beta: &beta
<<: *docker-base
only:
- /^v([0-9\.]+b\d+)$/
except:
- branches
.beta-vars: &beta-vars
RELEASE: YES
BETA: YES
.dev: &dev
<<: *docker-base
only:
- dev
.dev-vars: &dev-vars
DEV: YES
#aarch64-beta-docker:
# <<: *beta
# variables:
# BETA: "YES"
# BUILD_ARCH: aarch64
# IS_HASSIO: "NO"
# RELEASE: "YES"
#aarch64-beta-hassio:
# <<: *beta
# variables:
# BETA: "YES"
# BUILD_ARCH: aarch64
# IS_HASSIO: "YES"
# RELEASE: "YES"
#aarch64-dev-docker:
# <<: *dev
# variables:
# BUILD_ARCH: aarch64
# DEV: "YES"
# IS_HASSIO: "NO"
#aarch64-dev-hassio:
# <<: *dev
# variables:
# BUILD_ARCH: aarch64
# DEV: "YES"
# IS_HASSIO: "YES"
#aarch64-latest-docker:
# <<: *latest
# variables:
# BETA: "YES"
# BUILD_ARCH: aarch64
# IS_HASSIO: "NO"
# LATEST: "YES"
# RELEASE: "YES"
#aarch64-latest-hassio:
# <<: *latest
# variables:
# BETA: "YES"
# BUILD_ARCH: aarch64
# IS_HASSIO: "YES"
# LATEST: "YES"
# RELEASE: "YES"
amd64-beta-docker:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: amd64
IS_HASSIO: "NO"
RELEASE: "YES"
amd64-beta-hassio:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: amd64
IS_HASSIO: "YES"
RELEASE: "YES"
amd64-dev-docker:
<<: *dev
variables:
BUILD_ARCH: amd64
DEV: "YES"
IS_HASSIO: "NO"
amd64-dev-hassio:
<<: *dev
variables:
BUILD_ARCH: amd64
DEV: "YES"
IS_HASSIO: "YES"
amd64-latest-docker:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: amd64
IS_HASSIO: "NO"
LATEST: "YES"
RELEASE: "YES"
amd64-latest-hassio:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: amd64
IS_HASSIO: "YES"
LATEST: "YES"
RELEASE: "YES"
armhf-beta-docker:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: armhf
IS_HASSIO: "NO"
RELEASE: "YES"
armhf-beta-hassio:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: armhf
IS_HASSIO: "YES"
RELEASE: "YES"
armhf-dev-docker:
<<: *dev
variables:
BUILD_ARCH: armhf
DEV: "YES"
IS_HASSIO: "NO"
armhf-dev-hassio:
<<: *dev
variables:
BUILD_ARCH: armhf
DEV: "YES"
IS_HASSIO: "YES"
armhf-latest-docker:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: armhf
IS_HASSIO: "NO"
LATEST: "YES"
RELEASE: "YES"
armhf-latest-hassio:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: armhf
IS_HASSIO: "YES"
LATEST: "YES"
RELEASE: "YES"
i386-beta-docker:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: i386
IS_HASSIO: "NO"
RELEASE: "YES"
i386-beta-hassio:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: i386
IS_HASSIO: "YES"
RELEASE: "YES"
i386-dev-docker:
<<: *dev
variables:
BUILD_ARCH: i386
DEV: "YES"
IS_HASSIO: "NO"
i386-dev-hassio:
<<: *dev
variables:
BUILD_ARCH: i386
DEV: "YES"
IS_HASSIO: "YES"
i386-latest-docker:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: i386
IS_HASSIO: "NO"
LATEST: "YES"
RELEASE: "YES"
i386-latest-hassio:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: i386
IS_HASSIO: "YES"
LATEST: "YES"
RELEASE: "YES"

38
.travis.yml Normal file
View File

@@ -0,0 +1,38 @@
sudo: false
language: python
cache:
directories:
- "~/.platformio"
- "$TRAVIS_BUILD_DIR/tests/build/test1/.piolibdeps"
- "$TRAVIS_BUILD_DIR/tests/build/test2/.piolibdeps"
- "$TRAVIS_BUILD_DIR/tests/build/test3/.piolibdeps"
matrix:
fast_finish: true
include:
- python: "2.7"
env: TARGET=Lint2.7
install: pip install -e . && pip install flake8==3.6.0 pylint==1.9.4 pillow
script:
- flake8 esphome
- pylint esphome
- python: "3.5.3"
env: TARGET=Lint3.5
install: pip install -U https://github.com/platformio/platformio-core/archive/develop.zip && pip install -e . && pip install flake8==3.6.0 pylint==2.3.0 pillow
script:
- flake8 esphome
- pylint esphome
- python: "2.7"
env: TARGET=Test2.7
install: pip install -e . && pip install flake8==3.6.0 pylint==1.9.4 pillow
script:
- esphome tests/test1.yaml compile
- esphome tests/test2.yaml compile
- esphome tests/test3.yaml compile
#- python: "3.5.3"
# env: TARGET=Test3.5
# install: pip install -U https://github.com/platformio/platformio-core/archive/develop.zip && pip install -e . && pip install flake8==3.6.0 pylint==2.3.0 pillow
# script:
# - esphome tests/test1.yaml compile
# - esphome tests/test2.yaml compile

46
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@otto-winter.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

18
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,18 @@
# Contributing to ESPHome
This python project is responsible for reading in YAML configuration files,
converting them to C++ code. This code is then converted to a platformio project and compiled
with [esphome-core](https://github.com/esphome/esphome-core), the C++ framework behind the project.
For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphomeyaml
Things to note when contributing:
- Please test your changes :)
- If a new feature is added or an existing user-facing feature is changed, you should also
update the [docs](https://github.com/esphome/esphome-docs). See [contributing to esphome-docs](https://esphome.io/guides/contributing.html#contributing-to-esphomedocs)
for more information.
- Please also update the tests in the `tests/` folder. You can do so by just adding a line in one of the YAML files
which checks if your new feature compiles correctly.
- Sometimes I will let pull requests linger because I'm not 100% sure about them. Please feel free to ping
me after some time.

View File

@@ -1,25 +0,0 @@
FROM python:2.7
MAINTAINER Otto Winter <contact@otto-winter.com>
ENV ESPHOMEYAML_OTA_HOST_PORT=6123
EXPOSE 6123
VOLUME /config
WORKDIR /usr/src/app
COPY requirements.txt /usr/src/app/
RUN pip install --no-cache-dir -r requirements.txt
COPY docker/platformio.ini /usr/src/app/
RUN platformio settings set enable_telemetry No && \
platformio lib --global install esphomelib=https://github.com/OttoWinter/esphomelib.git#v1.2.1 && \
platformio run -e espressif32 -e espressif8266; exit 0
# Fix issue with static IP on ESP32: https://github.com/espressif/arduino-esp32/issues/1081
RUN curl https://github.com/espressif/arduino-esp32/commit/144480637a718844b8f48f4392da8d4f622f2e5e.patch | \
patch /root/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiGeneric.cpp
COPY . .
RUN pip install -e .
WORKDIR /config
ENTRYPOINT ["esphomeyaml"]

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Otto Winter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17
MANIFEST.in Normal file
View File

@@ -0,0 +1,17 @@
include README.md
include esphome/dashboard/templates/index.html
include esphome/dashboard/templates/login.html
include esphome/dashboard/static/ace.js
include esphome/dashboard/static/esphome.css
include esphome/dashboard/static/esphome.js
include esphome/dashboard/static/favicon.ico
include esphome/dashboard/static/jquery.min.js
include esphome/dashboard/static/jquery.validate.min.js
include esphome/dashboard/static/jquery-ui.min.js
include esphome/dashboard/static/materialize.min.css
include esphome/dashboard/static/materialize.min.js
include esphome/dashboard/static/materialize-stepper.min.css
include esphome/dashboard/static/materialize-stepper.min.js
include esphome/dashboard/static/mode-yaml.js
include esphome/dashboard/static/theme-dreamweaver.js
include esphome/dashboard/static/ext-searchbox.js

View File

@@ -1,38 +1,9 @@
# esphomeyaml for [esphomelib](https://github.com/OttoWinter/esphomelib)
# ESPHome [![Build Status](https://travis-ci.org/esphome/esphome.svg?branch=master)](https://travis-ci.org/esphome/esphome) [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/)
### Getting Started Guide: https://esphomelib.com/esphomeyaml/getting-started.html
[![ESPHome Logo](https://esphome.io/_images/logo-text.png)](https://esphome.io/)
### Available Components: https://esphomelib.com/esphomeyaml/index.html
**Documentation:** https://esphome.io/
esphomeyaml is the solution for your ESP8266/ESP32 projects with Home Assistant. It allows you to create **custom firmwares** for your microcontrollers with no programming experience required. All you need to know is the YAML configuration format which is also used by Home Assistant.
For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues).
esphomeyaml will:
* Read your configuration file and warn you about potential errors (like using the invalid pins.)
* Create a custom C++ sketch file for you using esphomeyaml's powerful C++ generation engine.
* Compile the sketch file for you using [platformio](http://platformio.org/).
* Upload the binary to your ESP via Over the Air updates.
* Automatically start remote logs via MQTT.
And all of that with a single command 🎉:
```bash
esphomeyaml configuration.yaml run
```
## Features
* **No programming experience required:** just edit YAML configuration
files like you're used to with Home Assistant.
* **Flexible:** Use [esphomelib](https://github.com/OttoWinter/esphomelib)'s powerful core to create custom sensors/outputs.
* **Fast and efficient:** Written in C++ and keeps memory consumption to a minimum.
* **Made for Home Assistant:** Almost all Home Assistant features are supported out of the box. Including RGB lights and many more.
* **Easy reproducible configuration:** No need to go through a long setup process for every single node. Just copy a configuration file and run a single command.
* **Smart Over The Air Updates:** esphomeyaml has OTA updates deeply integrated into the system. It even automatically enters a recovery mode if a boot loop is detected.
* **Powerful logging engine:** View colorful logs and debug issues remotely.
* **Open Source**
* For me: Makes documenting esphomelib's features a lot easier.
## Special Thanks
Special Thanks to the Home Assistant project. Lots of the code base of esphomeyaml is based off of Home Assistant, for example the loading and config validation code.
For feature requests, please see [feature requests](https://github.com/esphome/feature-requests/issues).

10
docker/Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
ARG BUILD_FROM=esphome/esphome-base-amd64:1.3.0
FROM ${BUILD_FROM}
COPY . .
RUN \
pip2 install --no-cache-dir --no-binary :all: -e .
WORKDIR /config
ENTRYPOINT ["esphome"]
CMD ["/config", "dashboard"]

20
docker/Dockerfile.hassio Normal file
View File

@@ -0,0 +1,20 @@
ARG BUILD_FROM=esphome/esphome-hassio-base-amd64:1.3.0
FROM ${BUILD_FROM}
# Copy root filesystem
COPY docker/rootfs/ /
COPY setup.py setup.cfg MANIFEST.in /opt/esphome/
COPY esphome /opt/esphome/esphome
RUN \
pip2 install --no-cache-dir --no-binary :all: -e /opt/esphome
# Build arguments
ARG BUILD_VERSION=dev
# Labels
LABEL \
io.hass.name="ESPHome" \
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
io.hass.type="addon" \
io.hass.version=${BUILD_VERSION}

6
docker/Dockerfile.lint Normal file
View File

@@ -0,0 +1,6 @@
FROM python:2.7
COPY requirements.txt /requirements.txt
RUN pip install -r /requirements.txt && \
pip install flake8==3.6.0 pylint==1.9.4 pillow

21
docker/Dockerfile.test Normal file
View File

@@ -0,0 +1,21 @@
FROM ubuntu:bionic
RUN apt-get update && apt-get install -y --no-install-recommends \
python \
python-pip \
python-setuptools \
python-pil \
git \
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/*rm -rf /var/lib/apt/lists/* /tmp/* && \
pip install --no-cache-dir --no-binary :all: platformio && \
platformio settings set enable_telemetry No && \
platformio settings set check_libraries_interval 1000000 && \
platformio settings set check_platformio_interval 1000000 && \
platformio settings set check_platforms_interval 1000000
COPY docker/platformio.ini /pio/platformio.ini
RUN platformio run -d /pio; rm -rf /pio
COPY requirements.txt /requirements.txt
RUN pip install --no-cache-dir -r /requirements.txt

26
docker/hooks/build Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# the Docker repository tag being built.
declare CACHE_TAG
echo "CACHE_TAG: ${CACHE_TAG}"
# the name and tag of the Docker repository being built. (This variable is a combination of DOCKER_REPO:CACHE_TAG.)
declare IMAGE_NAME
echo "IMAGE_NAME: ${IMAGE_NAME}"
# the architecture to build
declare BUILD_ARCH
echo "BUILD_ARCH: ${BUILD_ARCH}"
# whether this is a hassio build
declare IS_HASSIO
echo "IS_HASSIO: ${IS_HASSIO}"
echo "PWD: $PWD"
if [[ ${IS_HASSIO} = "YES" ]]; then
docker build \
--build-arg "BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.3.0" \
--build-arg "BUILD_VERSION=${CACHE_TAG}" \
-t "${IMAGE_NAME}" -f ../docker/Dockerfile.hassio ..
else
docker build \
--build-arg "BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.3.0" \
-t "${IMAGE_NAME}" -f ../docker/Dockerfile ..
fi

18
docker/hooks/pre_build Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# the architecture to build
declare BUILD_ARCH
echo "BUILD_ARCH: ${BUILD_ARCH}"
if [[ ${BUILD_ARCH} = "amd64" ]]; then
echo "No qemu required..."
exit 0
fi
if [[ ${BUILD_ARCH} = "i386" ]]; then
echo "No qemu required..."
exit 0
fi
echo "Installing qemu..."
docker run --rm --privileged multiarch/qemu-user-static:register --reset

View File

@@ -1,12 +1,12 @@
; This file allows the docker build file to install the required platformio
; platforms
[env:espressif32]
platform = espressif32
board = nodemcu-32s
framework = arduino
[env:espressif8266]
platform = espressif8266
platform = espressif8266@1.8.0
board = nodemcuv2
framework = arduino
[env:espressif32]
platform = espressif32@1.5.0
board = nodemcu-32s
framework = arduino

View File

@@ -0,0 +1,35 @@
#!/usr/bin/with-contenv bash
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# This files check if all user configuration requirements are met
# ==============================================================================
# shellcheck disable=SC1091
source /usr/lib/hassio-addons/base.sh
# Check SSL requirements, if enabled
if hass.config.true 'ssl'; then
if ! hass.config.has_value 'certfile'; then
hass.die 'SSL is enabled, but no certfile was specified.'
fi
if ! hass.config.has_value 'keyfile'; then
hass.die 'SSL is enabled, but no keyfile was specified'
fi
if ! hass.file_exists "/ssl/$(hass.config.get 'certfile')"; then
if ! hass.file_exists "/ssl/$(hass.config.get 'keyfile')"; then
# Both files are missing, let's print a friendlier error message
text="You enabled encrypted connections using the \"ssl\": true option.
However, the SSL files \"$(hass.config.get 'certfile')\" and \"$(hass.config.get 'keyfile')\"
were not found. If you're using Hass.io on your local network and don't want
to encrypt connections to the ESPHome dashboard, you can manually disable
SSL by setting \"ssl\" to false."
hass.die "${text}"
fi
hass.die 'The configured certfile is not found'
fi
if ! hass.file_exists "/ssl/$(hass.config.get 'keyfile')"; then
hass.die 'The configured keyfile is not found'
fi
fi

View File

@@ -0,0 +1,28 @@
#!/usr/bin/with-contenv bash
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# Configures NGINX for use with ESPHome
# ==============================================================================
# shellcheck disable=SC1091
source /usr/lib/hassio-addons/base.sh
declare certfile
declare keyfile
declare port
mkdir -p /var/log/nginx
# Enable SSL
if hass.config.true 'ssl'; then
rm /etc/nginx/nginx.conf
mv /etc/nginx/nginx-ssl.conf /etc/nginx/nginx.conf
certfile=$(hass.config.get 'certfile')
keyfile=$(hass.config.get 'keyfile')
sed -i "s/%%certfile%%/${certfile}/g" /etc/nginx/nginx.conf
sed -i "s/%%keyfile%%/${keyfile}/g" /etc/nginx/nginx.conf
fi
port=$(hass.config.get 'port')
sed -i "s/%%port%%/${port}/g" /etc/nginx/nginx.conf

View File

@@ -0,0 +1,14 @@
#!/usr/bin/with-contenv bash
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# This files installs the user ESPHome version if specified
# ==============================================================================
# shellcheck disable=SC1091
source /usr/lib/hassio-addons/base.sh
declare esphome_version
if hass.config.has_value 'esphome_version'; then
esphome_version=$(hass.config.get 'esphome_version')
pip2 install --no-cache-dir --no-binary :all: "https://github.com/esphome/esphome/archive/${esphome_version}.zip"
fi

View File

@@ -0,0 +1,13 @@
#!/usr/bin/with-contenv bash
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# This files migrates the esphome config directory from the old path
# ==============================================================================
# shellcheck disable=SC1091
source /usr/lib/hassio-addons/base.sh
if [[ ! -d /config/esphome && -d /config/esphomeyaml ]]; then
echo "Moving config directory from /config/esphomeyaml to /config/esphome"
mv /config/esphomeyaml /config/esphome
mv /config/esphome/.esphomeyaml /config/esphome/.esphome
fi

View File

@@ -0,0 +1,62 @@
worker_processes 1;
pid /var/run/nginx.pid;
error_log stderr;
events {
worker_connections 1024;
}
http {
access_log stdout;
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream esphome {
ip_hash;
server unix:/var/run/esphome.sock;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
server_name hassio.local;
listen %%port%% default_server ssl;
root /dev/null;
ssl_certificate /ssl/%%certfile%%;
ssl_certificate_key /ssl/%%keyfile%%;
ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA;
ssl_ecdh_curve secp384r1;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
# Redirect http requests to https on the same port.
# https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/
error_page 497 https://$http_host$request_uri;
location / {
proxy_redirect off;
proxy_pass http://esphome;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Authorization "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
}
}
}

View File

@@ -0,0 +1,46 @@
worker_processes 1;
pid /var/run/nginx.pid;
error_log stderr;
events {
worker_connections 1024;
}
http {
access_log stdout;
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream esphome {
ip_hash;
server unix:/var/run/esphome.sock;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
server_name hassio.local;
listen %%port%% default_server;
root /dev/null;
location / {
proxy_redirect off;
proxy_pass http://esphome;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Authorization "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
}
}
}

View File

@@ -0,0 +1,9 @@
#!/usr/bin/execlineb -S0
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# Take down the S6 supervision tree when ESPHome fails
# ==============================================================================
if -n { s6-test $# -ne 0 }
if -n { s6-test ${1} -eq 256 }
s6-svscanctl -t /var/run/s6/services

View File

@@ -0,0 +1,28 @@
#!/usr/bin/with-contenv bash
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# Runs the ESPHome dashboard
# ==============================================================================
# shellcheck disable=SC1091
source /usr/lib/hassio-addons/base.sh
export ESPHOME_IS_HASSIO=true
if hass.config.true 'leave_front_door_open'; then
export DISABLE_HA_AUTHENTICATION=true
fi
if hass.config.true 'streamer_mode'; then
export ESPHOME_STREAMER_MODE=true
fi
if hass.config.true 'status_use_ping'; then
export ESPHOME_DASHBOARD_USE_PING=true
fi
if hass.config.has_value 'relative_url'; then
export ESPHOME_DASHBOARD_RELATIVE_URL=$(hass.config.get 'relative_url')
fi
hass.log.info "Starting ESPHome dashboard..."
exec esphome /config/esphome dashboard --socket /var/run/esphome.sock --hassio

View File

@@ -0,0 +1,9 @@
#!/usr/bin/execlineb -S0
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# Take down the S6 supervision tree when NGINX fails
# ==============================================================================
if -n { s6-test $# -ne 0 }
if -n { s6-test ${1} -eq 256 }
s6-svscanctl -t /var/run/s6/services

View File

@@ -0,0 +1,10 @@
#!/usr/bin/with-contenv bash
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# Runs the NGINX proxy
# ==============================================================================
# shellcheck disable=SC1091
source /usr/lib/hassio-addons/base.sh
hass.log.info "Starting NGINX..."
exec nginx -g "daemon off;"

521
esphome/__main__.py Normal file
View File

@@ -0,0 +1,521 @@
from __future__ import print_function
import argparse
from collections import OrderedDict
from datetime import datetime
import logging
import os
import random
import sys
from esphome import const, core_config, mqtt, platformio_api, wizard, writer, yaml_util
from esphome.api.client import run_logs
from esphome.config import get_component, iter_components, read_config, strip_default_ids
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_ESPHOME, CONF_LOGGER, \
CONF_USE_CUSTOM_CODE
from esphome.core import CORE, EsphomeError
from esphome.cpp_generator import Expression, RawStatement, add, statement
from esphome.helpers import color, indent
from esphome.py_compat import IS_PY2, safe_input, text_type
from esphome.storage_json import StorageJSON, storage_path
from esphome.util import run_external_command, run_external_process, safe_print, \
is_dev_esphome_version
_LOGGER = logging.getLogger(__name__)
PRE_INITIALIZE = ['esphome', 'logger', 'wifi', 'ethernet', 'ota', 'mqtt', 'web_server', 'api',
'i2c']
def get_serial_ports():
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
from serial.tools.list_ports import comports
result = []
for port, desc, info in comports():
if not port:
continue
if "VID:PID" in info:
result.append((port, desc))
result.sort(key=lambda x: x[0])
return result
def choose_prompt(options):
if not options:
raise ValueError
if len(options) == 1:
return options[0][1]
safe_print(u"Found multiple options, please choose one:")
for i, (desc, _) in enumerate(options):
safe_print(u" [{}] {}".format(i + 1, desc))
while True:
opt = safe_input('(number): ')
if opt in options:
opt = options.index(opt)
break
try:
opt = int(opt)
if opt < 1 or opt > len(options):
raise ValueError
break
except ValueError:
safe_print(color('red', u"Invalid option: '{}'".format(opt)))
return options[opt - 1][1]
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
options = []
for res, desc in get_serial_ports():
options.append((u"{} ({})".format(res, desc), res))
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
options.append((u"Over The Air ({})".format(CORE.address), CORE.address))
if default == 'OTA':
return CORE.address
if show_mqtt and 'mqtt' in CORE.config:
options.append((u"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]:
return check_default
return choose_prompt(options)
def get_port_type(port):
if port.startswith('/') or port.startswith('COM'):
return 'SERIAL'
if port == 'MQTT':
return 'MQTT'
return 'NETWORK'
def run_miniterm(config, port):
import serial
if CONF_LOGGER not in config:
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
return
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)
backtrace_state = False
with serial.Serial(port, baudrate=baud_rate) as ser:
while True:
try:
raw = ser.readline()
except serial.SerialException:
_LOGGER.error("Serial port closed!")
return
if IS_PY2:
line = raw.replace('\r', '').replace('\n', '')
else:
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)
def write_cpp(config):
_LOGGER.info("Generating C++ source...")
CORE.add_job(core_config.to_code, config[CONF_ESPHOME], domain='esphome')
for domain in PRE_INITIALIZE:
if domain == CONF_ESPHOME or domain not in config:
continue
CORE.add_job(get_component(domain).to_code, config[domain], domain=domain)
for domain, component, conf in iter_components(config):
if domain in PRE_INITIALIZE or not hasattr(component, 'to_code'):
continue
CORE.add_job(component.to_code, conf, domain=domain)
CORE.flush_tasks()
add(RawStatement(''))
add(RawStatement(''))
all_code = []
for exp in CORE.expressions:
if not config[CONF_ESPHOME][CONF_USE_CUSTOM_CODE]:
if isinstance(exp, Expression) and not exp.required:
continue
all_code.append(text_type(statement(exp)))
writer.write_platformio_project()
code_s = indent('\n'.join(line.rstrip() for line in all_code))
writer.write_cpp(code_s)
return 0
def compile_program(args, config):
_LOGGER.info("Compiling app...")
rc = platformio_api.run_compile(config, args.verbose)
if rc != 0 and CORE.is_dev_esphome_core_version and not is_dev_esphome_version():
_LOGGER.warning("You're using 'esphome_core_version: dev' but not using the "
"dev version of the ESPHome tool.")
_LOGGER.warning("Expect compile errors if these versions are out of sync.")
_LOGGER.warning("Please install the dev version of ESPHome too when using "
"'esphome_core_version: dev'.")
_LOGGER.warning(" - Hass.io: Install 'ESPHome (dev)' addon")
_LOGGER.warning(" - Docker: docker run [...] esphome/esphome:dev [...]")
_LOGGER.warning(" - PIP: pip install -U https://github.com/esphome/esphome/archive/dev.zip")
return rc
def upload_using_esptool(config, port):
path = CORE.firmware_bin
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
'--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)
return run_external_process(*cmd)
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 CORE.is_esp8266:
return upload_using_esptool(config, host)
return platformio_api.run_upload(config, args.verbose, host)
from esphome.components import ota
from esphome import espota2
if args.host_port is not None:
host_port = args.host_port
else:
host_port = int(os.getenv('ESPHOME_OTA_HOST_PORT', random.randint(10000, 60000)))
verbose = args.verbose
remote_port = ota.get_port(config)
password = ota.get_auth(config)
storage = StorageJSON.load(storage_path())
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
if res == 0:
if storage is not None and storage.use_legacy_ota:
storage.use_legacy_ota = False
storage.save(storage_path())
return res
if storage is not None and not storage.use_legacy_ota:
return res
_LOGGER.warning("OTA v2 method failed. Trying with legacy OTA...")
return espota2.run_legacy_ota(verbose, host_port, host, remote_port, password,
CORE.firmware_bin)
def show_logs(config, args, port):
if 'logger' not in config:
raise EsphomeError("Logger is not configured!")
if get_port_type(port) == 'SERIAL':
run_miniterm(config, port)
return 0
if get_port_type(port) == 'NETWORK' and 'api' in config:
return run_logs(config, port)
if get_port_type(port) == 'MQTT' and 'mqtt' in config:
return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id)
raise ValueError
def clean_mqtt(config, args):
return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id)
def setup_log(debug=False):
log_level = logging.DEBUG if debug else logging.INFO
logging.basicConfig(level=log_level)
fmt = "%(levelname)s %(message)s"
colorfmt = "%(log_color)s{}%(reset)s".format(fmt)
datefmt = '%H:%M:%S'
logging.getLogger('urllib3').setLevel(logging.WARNING)
try:
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',
}
))
except ImportError:
pass
def command_wizard(args):
return wizard.wizard(args.configuration)
def command_config(args, config):
_LOGGER.info("Configuration is valid!")
if not args.verbose:
config = strip_default_ids(config)
safe_print(yaml_util.dump(config))
return 0
def command_compile(args, config):
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
if args.only_generate:
_LOGGER.info(u"Successfully generated source code.")
return 0
exit_code = compile_program(args, config)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully compiled program.")
return 0
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)
exit_code = upload_program(config, args, port)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully uploaded program.")
return 0
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)
return show_logs(config, args, port)
def command_run(args, config):
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
exit_code = compile_program(args, config)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully compiled program.")
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(u"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)
return show_logs(config, args, port)
def command_clean_mqtt(args, config):
return clean_mqtt(config, args)
def command_mqtt_fingerprint(args, config):
return mqtt.get_fingerprint(config)
def command_version(args):
safe_print(u"Version: {}".format(const.__version__))
return 0
def command_clean(args, config):
try:
writer.clean_build()
except OSError as err:
_LOGGER.error("Error deleting build files: %s", err)
return 1
_LOGGER.info("Done!")
return 0
def command_hass_config(args, config):
from esphome.components import mqtt as mqtt_component
_LOGGER.info("This is what you should put in your Home Assistant YAML configuration.")
_LOGGER.info("Please note this is only necessary if you're not using MQTT discovery.")
data = mqtt_component.GenerateHassConfigData(config)
hass_config = OrderedDict()
for domain, component, conf in iter_components(config):
if not hasattr(component, 'to_hass_config'):
continue
func = getattr(component, 'to_hass_config')
ret = func(data, conf)
if not isinstance(ret, (list, tuple)):
ret = [ret]
ret = [x for x in ret if x is not None]
domain_conf = hass_config.setdefault(domain.split('.')[0], [])
domain_conf += ret
safe_print(yaml_util.dump(hass_config))
return 0
def command_dashboard(args):
from esphome.dashboard import dashboard
return dashboard.start_web_server(args)
PRE_CONFIG_ACTIONS = {
'wizard': command_wizard,
'version': command_version,
'dashboard': command_dashboard
}
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,
'hass-config': command_hass_config,
}
def parse_args(argv):
parser = argparse.ArgumentParser(prog='esphome')
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
action='store_true')
parser.add_argument('--dashboard', help="Internal flag to set if the command is run from the "
"dashboard.", action='store_true')
parser.add_argument('configuration', help='Your YAML configuration file.')
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_upload.add_argument('--host-port', help="Specify the host port.", type=int)
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('--host-port', help="Specify the host port to use for OTA", type=int)
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("--password", help="The optional password to require for all requests.",
type=str, default='')
dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
action='store_true')
dashboard.add_argument("--hassio",
help="Internal flag used to tell esphome is started as a Hass.io "
"add-on.",
action="store_true")
dashboard.add_argument("--socket",
help="Make the dashboard serve under a unix socket", type=str)
subparsers.add_parser('hass-config',
help="Dump the configuration entries that should be added "
"to Home Assistant when not using MQTT discovery.")
return parser.parse_args(argv[1:])
def run_esphome(argv):
args = parse_args(argv)
CORE.dashboard = args.dashboard
setup_log(args.verbose)
if args.command in PRE_CONFIG_ACTIONS:
try:
return PRE_CONFIG_ACTIONS[args.command](args)
except EsphomeError as e:
_LOGGER.error(e)
return 1
CORE.config_path = args.configuration
config = read_config(args.verbose)
if config is None:
return 1
CORE.config = config
if args.command in POST_CONFIG_ACTIONS:
try:
return POST_CONFIG_ACTIONS[args.command](args, config)
except EsphomeError as e:
_LOGGER.error(e)
return 1
safe_print(u"Unknown command {}".format(args.command))
return 1
def main():
try:
return run_esphome(sys.argv)
except EsphomeError as e:
_LOGGER.error(e)
return 1
except KeyboardInterrupt:
return 1
if __name__ == "__main__":
sys.exit(main())

330
esphome/api/api.proto Normal file
View File

@@ -0,0 +1,330 @@
syntax = "proto3";
// 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:
// * 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
// The connection is established in 4 steps:
// * First, the client connects to the server and sends a "Hello Request" identifying itself
// * The server responds with a "Hello Response" and selects the protocol version
// * After receiving this message, the client attempts to authenticate itself using
// the password and a "Connect Request"
// * The server responds with a "Connect Response" and notifies of invalid password.
// If anything in this initial process fails, the connection must immediately closed
// by both sides and _no_ disconnection message is to be sent.
// Message sent at the beginning of each connection
// Can only be sent by the client and only at the beginning of the connection
message HelloRequest {
// Description of client (like User Agent)
// For example "Home Assistant"
// Not strictly necessary to send but nice for debugging
// purposes.
string client_info = 1;
}
// Confirmation of successful connection request.
// Can only be sent by the server and only at the beginning of the connection
message HelloResponse {
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
// for compatibility and if necessary adopt to an older API.
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
// Minor is for breaking changes in individual messages - a mismatch will lead to a warning message
uint32 api_version_major = 1;
uint32 api_version_minor = 2;
// A string identifying the server (ESP); like client info this may be empty
// and only exists for debugging/logging purposes.
// For example "ESPHome v1.10.0 on ESP8266"
string server_info = 3;
}
// Message sent at the beginning of each connection to authenticate the client
// Can only be sent by the client and only at the beginning of the connection
message ConnectRequest {
// The password to log in with
string password = 1;
}
// Confirmation of successful connection. After this the connection is available for all traffic.
// Can only be sent by the server and only at the beginning of the connection
message ConnectResponse {
bool invalid_password = 1;
}
// Request to close the connection.
// Can be sent by both the client and server
message DisconnectRequest {
// Do not close the connection before the acknowledgement arrives
}
message DisconnectResponse {
// Empty - Both parties are required to close the connection after this
// message has been received.
}
message PingRequest {
// Empty
}
message PingResponse {
// Empty
}
message DeviceInfoRequest {
// Empty
}
message DeviceInfoResponse {
bool uses_password = 1;
// The name of the node, given by "App.set_name()"
string name = 2;
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
string mac_address = 3;
// A string describing the ESPHome version. For example "1.10.0"
string esphome_core_version = 4;
// A string describing the date of compilation, this is generated by the compiler
// and therefore may not be in the same format all the time.
// If the user isn't using esphome, this will also not be set.
string compilation_time = 5;
// The model of the board. For example NodeMCU
string model = 6;
bool has_deep_sleep = 7;
}
message ListEntitiesRequest {
// Empty
}
message ListEntitiesBinarySensorResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string device_class = 5;
bool is_status_binary_sensor = 6;
}
message ListEntitiesCoverResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool is_optimistic = 5;
}
message ListEntitiesFanResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool supports_oscillation = 5;
bool supports_speed = 6;
}
message ListEntitiesLightResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool supports_brightness = 5;
bool supports_rgb = 6;
bool supports_white_value = 7;
bool supports_color_temperature = 8;
float min_mireds = 9;
float max_mireds = 10;
repeated string effects = 11;
}
message ListEntitiesSensorResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
string unit_of_measurement = 6;
int32 accuracy_decimals = 7;
}
message ListEntitiesSwitchResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool optimistic = 6;
}
message ListEntitiesTextSensorResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
}
message ListEntitiesDoneResponse {
// Empty
}
message SubscribeStatesRequest {
// Empty
}
message BinarySensorStateResponse {
fixed32 key = 1;
bool state = 2;
}
message CoverStateResponse {
fixed32 key = 1;
enum CoverState {
OPEN = 0;
CLOSED = 1;
}
CoverState state = 2;
}
enum FanSpeed {
LOW = 0;
MEDIUM = 1;
HIGH = 2;
}
message FanStateResponse {
fixed32 key = 1;
bool state = 2;
bool oscillating = 3;
FanSpeed speed = 4;
}
message LightStateResponse {
fixed32 key = 1;
bool state = 2;
float brightness = 3;
float red = 4;
float green = 5;
float blue = 6;
float white = 7;
float color_temperature = 8;
string effect = 9;
}
message SensorStateResponse {
fixed32 key = 1;
float state = 2;
}
message SwitchStateResponse {
fixed32 key = 1;
bool state = 2;
}
message TextSensorStateResponse {
fixed32 key = 1;
string state = 2;
}
message CoverCommandRequest {
fixed32 key = 1;
enum CoverCommand {
OPEN = 0;
CLOSE = 1;
STOP = 2;
}
bool has_state = 2;
CoverCommand command = 3;
}
message FanCommandRequest {
fixed32 key = 1;
bool has_state = 2;
bool state = 3;
bool has_speed = 4;
FanSpeed speed = 5;
bool has_oscillating = 6;
bool oscillating = 7;
}
message LightCommandRequest {
fixed32 key = 1;
bool has_state = 2;
bool state = 3;
bool has_brightness = 4;
float brightness = 5;
bool has_rgb = 6;
float red = 7;
float green = 8;
float blue = 9;
bool has_white = 10;
float white = 11;
bool has_color_temperature = 12;
float color_temperature = 13;
bool has_transition_length = 14;
uint32 transition_length = 15;
bool has_flash_length = 16;
uint32 flash_length = 17;
bool has_effect = 18;
string effect = 19;
}
message SwitchCommandRequest {
fixed32 key = 1;
bool state = 2;
}
enum LogLevel {
NONE = 0;
ERROR = 1;
WARN = 2;
INFO = 3;
DEBUG = 4;
VERBOSE = 5;
VERY_VERBOSE = 6;
}
message SubscribeLogsRequest {
LogLevel level = 1;
bool dump_config = 2;
}
message SubscribeLogsResponse {
LogLevel level = 1;
string tag = 2;
string message = 3;
bool send_failed = 4;
}
message SubscribeServiceCallsRequest {
}
message ServiceCallResponse {
string service = 1;
map<string, string> data = 2;
map<string, string> data_template = 3;
map<string, string> variables = 4;
}
// 1. Client sends SubscribeHomeAssistantStatesRequest
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
// 3. Client sends HomeAssistantStateResponse for state changes.
message SubscribeHomeAssistantStatesRequest {
}
message SubscribeHomeAssistantStateResponse {
string entity_id = 1;
}
message HomeAssistantStateResponse {
string entity_id = 1;
string state = 2;
}
message GetTimeRequest {
}
message GetTimeResponse {
fixed32 epoch_seconds = 1;
}

2484
esphome/api/api_pb2.py Normal file

File diff suppressed because one or more lines are too long

495
esphome/api/client.py Normal file
View File

@@ -0,0 +1,495 @@
from datetime import datetime
import functools
import logging
import socket
import threading
import time
# pylint: disable=unused-import
from typing import Optional # noqa
from google.protobuf import message # noqa
from esphome import const
import esphome.api.api_pb2 as pb
from esphome.const import CONF_PASSWORD, CONF_PORT
from esphome.core import EsphomeError
from esphome.helpers import resolve_ip_address, indent, color
from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte, format_bytes
from esphome.util import safe_print
_LOGGER = logging.getLogger(__name__)
class APIConnectionError(EsphomeError):
pass
MESSAGE_TYPE_TO_PROTO = {
1: pb.HelloRequest,
2: pb.HelloResponse,
3: pb.ConnectRequest,
4: pb.ConnectResponse,
5: pb.DisconnectRequest,
6: pb.DisconnectResponse,
7: pb.PingRequest,
8: pb.PingResponse,
9: pb.DeviceInfoRequest,
10: pb.DeviceInfoResponse,
11: pb.ListEntitiesRequest,
12: pb.ListEntitiesBinarySensorResponse,
13: pb.ListEntitiesCoverResponse,
14: pb.ListEntitiesFanResponse,
15: pb.ListEntitiesLightResponse,
16: pb.ListEntitiesSensorResponse,
17: pb.ListEntitiesSwitchResponse,
18: pb.ListEntitiesTextSensorResponse,
19: pb.ListEntitiesDoneResponse,
20: pb.SubscribeStatesRequest,
21: pb.BinarySensorStateResponse,
22: pb.CoverStateResponse,
23: pb.FanStateResponse,
24: pb.LightStateResponse,
25: pb.SensorStateResponse,
26: pb.SwitchStateResponse,
27: pb.TextSensorStateResponse,
28: pb.SubscribeLogsRequest,
29: pb.SubscribeLogsResponse,
30: pb.CoverCommandRequest,
31: pb.FanCommandRequest,
32: pb.LightCommandRequest,
33: pb.SwitchCommandRequest,
34: pb.SubscribeServiceCallsRequest,
35: pb.ServiceCallResponse,
36: pb.GetTimeRequest,
37: pb.GetTimeResponse,
}
def _varuint_to_bytes(value):
if value <= 0x7F:
return byte_to_bytes(value)
ret = bytes()
while value:
temp = value & 0x7F
value >>= 7
if value:
ret += byte_to_bytes(temp | 0x80)
else:
ret += byte_to_bytes(temp)
return ret
def _bytes_to_varuint(value):
result = 0
bitpos = 0
for c in value:
val = char_to_byte(c)
result |= (val & 0x7F) << bitpos
bitpos += 7
if (val & 0x80) == 0:
return result
return None
# pylint: disable=too-many-instance-attributes,not-callable
class APIClient(threading.Thread):
def __init__(self, address, port, password):
threading.Thread.__init__(self)
self._address = address # type: str
self._port = port # type: int
self._password = password # type: Optional[str]
self._socket = None # type: Optional[socket.socket]
self._socket_open_event = threading.Event()
self._socket_write_lock = threading.Lock()
self._connected = False
self._authenticated = False
self._message_handlers = []
self._keepalive = 5
self._ping_timer = None
self._refresh_ping()
self.on_disconnect = None
self.on_connect = None
self.on_login = None
self.auto_reconnect = False
self._running_event = threading.Event()
self._stop_event = threading.Event()
@property
def stopped(self):
return self._stop_event.is_set()
def _refresh_ping(self):
if self._ping_timer is not None:
self._ping_timer.cancel()
self._ping_timer = None
def func():
self._ping_timer = None
if self._connected:
try:
self.ping()
except APIConnectionError:
self._fatal_error()
else:
self._refresh_ping()
self._ping_timer = threading.Timer(self._keepalive, func)
self._ping_timer.start()
def _cancel_ping(self):
if self._ping_timer is not None:
self._ping_timer.cancel()
self._ping_timer = None
def _close_socket(self):
self._cancel_ping()
if self._socket is not None:
self._socket.close()
self._socket = None
self._socket_open_event.clear()
self._connected = False
self._authenticated = False
self._message_handlers = []
def stop(self, force=False):
if self.stopped:
raise ValueError
if self._connected and not force:
try:
self.disconnect()
except APIConnectionError:
pass
self._close_socket()
self._stop_event.set()
if not force:
self.join()
def connect(self):
if not self._running_event.wait(0.1):
raise APIConnectionError("You need to call start() first!")
if self._connected:
raise APIConnectionError("Already connected!")
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)")
raise APIConnectionError(err)
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._socket.settimeout(10.0)
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
try:
self._socket.connect((ip, self._port))
except socket.error as err:
self._fatal_error()
raise APIConnectionError("Error connecting to {}: {}".format(ip, err))
self._socket.settimeout(0.1)
self._socket_open_event.set()
hello = pb.HelloRequest()
hello.client_info = 'ESPHome v{}'.format(const.__version__)
try:
resp = self._send_message_await_response(hello, pb.HelloResponse)
except APIConnectionError as err:
self._fatal_error()
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)
self._connected = True
if self.on_connect is not None:
self.on_connect()
def _check_connected(self):
if not self._connected:
self._fatal_error()
raise APIConnectionError("Must be connected!")
def login(self):
self._check_connected()
if self._authenticated:
raise APIConnectionError("Already logged in!")
connect = pb.ConnectRequest()
if self._password is not None:
connect.password = self._password
resp = self._send_message_await_response(connect, pb.ConnectResponse)
if resp.invalid_password:
raise APIConnectionError("Invalid password!")
self._authenticated = True
if self.on_login is not None:
self.on_login()
def _fatal_error(self):
was_connected = self._connected
self._close_socket()
if was_connected and self.on_disconnect is not None:
self.on_disconnect()
def _write(self, data): # type: (bytes) -> None
if self._socket is None:
raise APIConnectionError("Socket closed")
_LOGGER.debug("Write: %s", format_bytes(data))
with self._socket_write_lock:
try:
self._socket.sendall(data)
except socket.error as err:
self._fatal_error()
raise APIConnectionError("Error while writing data: {}".format(err))
def _send_message(self, msg):
# type: (message.Message) -> None
for message_type, klass in MESSAGE_TYPE_TO_PROTO.items():
if isinstance(msg, klass):
break
else:
raise ValueError
encoded = msg.SerializeToString()
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(text_type(msg)))
if IS_PY2:
req = chr(0x00)
else:
req = bytes([0])
req += _varuint_to_bytes(len(encoded))
req += _varuint_to_bytes(message_type)
req += encoded
self._write(req)
self._refresh_ping()
def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=1):
event = threading.Event()
responses = []
def on_message(resp):
if do_append(resp):
responses.append(resp)
if do_stop(resp):
event.set()
self._message_handlers.append(on_message)
self._send_message(send_msg)
ret = event.wait(timeout)
try:
self._message_handlers.remove(on_message)
except ValueError:
pass
if not ret:
raise APIConnectionError("Timeout while waiting for message response!")
return responses
def _send_message_await_response(self, send_msg, response_type, timeout=1):
def is_response(msg):
return isinstance(msg, response_type)
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)
def ping(self):
self._check_connected()
return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
def disconnect(self):
self._check_connected()
try:
self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse)
except APIConnectionError:
pass
self._close_socket()
if self.on_disconnect is not None:
self.on_disconnect()
def _check_authenticated(self):
if not self._authenticated:
raise APIConnectionError("Must login first!")
def subscribe_logs(self, on_log, log_level=None, dump_config=False):
self._check_authenticated()
def on_msg(msg):
if isinstance(msg, pb.SubscribeLogsResponse):
on_log(msg)
self._message_handlers.append(on_msg)
req = pb.SubscribeLogsRequest(dump_config=dump_config)
if log_level is not None:
req.level = log_level
self._send_message(req)
def _recv(self, amount):
ret = bytes()
if amount == 0:
return ret
while len(ret) < amount:
if self.stopped:
raise APIConnectionError("Stopped!")
if not self._socket_open_event.is_set():
raise APIConnectionError("No socket!")
try:
val = self._socket.recv(amount - len(ret))
except AttributeError:
raise APIConnectionError("Socket was closed")
except socket.timeout:
continue
except socket.error as err:
raise APIConnectionError("Error while receiving data: {}".format(err))
ret += val
return ret
def _recv_varint(self):
raw = bytes()
while not raw or char_to_byte(raw[-1]) & 0x80:
raw += self._recv(1)
return _bytes_to_varuint(raw)
def _run_once(self):
if not self._socket_open_event.wait(0.1):
return
# Preamble
if char_to_byte(self._recv(1)[0]) != 0x00:
raise APIConnectionError("Invalid preamble")
length = self._recv_varint()
msg_type = self._recv_varint()
raw_msg = self._recv(length)
if msg_type not in MESSAGE_TYPE_TO_PROTO:
_LOGGER.debug("Skipping message type %s", msg_type)
return
msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
msg.ParseFromString(raw_msg)
_LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg)))
for msg_handler in self._message_handlers[:]:
msg_handler(msg)
self._handle_internal_messages(msg)
self._refresh_ping()
def run(self):
self._running_event.set()
while not self.stopped:
try:
self._run_once()
except APIConnectionError as err:
if self.stopped:
break
if self._connected:
_LOGGER.error("Error while reading incoming messages: %s", err)
self._fatal_error()
self._running_event.clear()
def _handle_internal_messages(self, msg):
if isinstance(msg, pb.DisconnectRequest):
self._send_message(pb.DisconnectResponse())
if self._socket is not None:
self._socket.close()
self._socket = None
self._connected = False
if self.on_disconnect is not None:
self.on_disconnect()
elif isinstance(msg, pb.PingRequest):
self._send_message(pb.PingResponse())
elif isinstance(msg, pb.GetTimeRequest):
resp = pb.GetTimeResponse()
resp.epoch_seconds = int(time.time())
self._send_message(resp)
def run_logs(config, address):
conf = config['api']
port = conf[CONF_PORT]
password = conf[CONF_PASSWORD]
_LOGGER.info("Starting log output from %s using esphome API", address)
cli = APIClient(address, port, password)
stopping = False
retry_timer = []
has_connects = []
def try_connect(tries=0, is_disconnect=True):
if stopping:
return
if is_disconnect:
_LOGGER.warning(u"Disconnected from API.")
while retry_timer:
retry_timer.pop(0).cancel()
error = None
try:
cli.connect()
cli.login()
except APIConnectionError as err: # noqa
error = err
if error is None:
_LOGGER.info("Successfully connected to %s", address)
return
wait_time = min(2**tries, 300)
if not has_connects:
_LOGGER.warning(u"Initial connection failed. The ESP might not be connected "
u"to WiFi yet (%s). Re-Trying in %s seconds",
error, wait_time)
else:
_LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
error, wait_time)
timer = threading.Timer(wait_time, functools.partial(try_connect, tries + 1, is_disconnect))
timer.start()
retry_timer.append(timer)
def on_log(msg):
time_ = datetime.now().time().strftime(u'[%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)')
safe_print(time_ + text)
def on_login():
try:
cli.subscribe_logs(on_log, dump_config=not has_connects)
has_connects.append(True)
except APIConnectionError:
cli.disconnect()
cli.on_disconnect = try_connect
cli.on_login = on_login
cli.start()
try:
try_connect(is_disconnect=False)
while True:
time.sleep(1)
except KeyboardInterrupt:
stopping = True
cli.stop(True)
while retry_timer:
retry_timer.pop(0).cancel()
return 0

384
esphome/automation.py Normal file
View File

@@ -0,0 +1,384 @@
import copy
import voluptuous as vol
import esphome.config_validation as cv
from esphome.const import CONF_ABOVE, CONF_ACTION_ID, CONF_AND, CONF_AUTOMATION_ID, CONF_BELOW, \
CONF_CONDITION, CONF_CONDITION_ID, CONF_DELAY, CONF_ELSE, CONF_ID, CONF_IF, CONF_LAMBDA, \
CONF_OR, CONF_RANGE, CONF_THEN, CONF_TRIGGER_ID, CONF_WAIT_UNTIL, CONF_WHILE
from esphome.core import CORE
from esphome.cpp_generator import Pvariable, TemplateArguments, add, get_variable, \
process_lambda, templatable
from esphome.cpp_types import Action, App, Component, PollingComponent, Trigger, bool_, \
esphome_ns, float_, uint32, void
from esphome.util import ServiceRegistry
def maybe_simple_id(*validators):
validator = vol.All(*validators)
def validate(value):
if isinstance(value, dict):
return validator(value)
return validator({CONF_ID: value})
return validate
def validate_recursive_condition(value):
is_list = isinstance(value, list)
value = cv.ensure_list()(value)[:]
for i, item in enumerate(value):
path = [i] if is_list else []
item = copy.deepcopy(item)
if not isinstance(item, dict):
raise vol.Invalid(u"Condition must consist of key-value mapping! Got {}".format(item),
path)
key = next((x for x in item if x != CONF_CONDITION_ID), None)
if key is None:
raise vol.Invalid(u"Key missing from action! Got {}".format(item), path)
if key not in CONDITION_REGISTRY:
raise vol.Invalid(u"Unable to find condition with the name '{}', is the "
u"component loaded?".format(key), path + [key])
item.setdefault(CONF_CONDITION_ID, None)
key2 = next((x for x in item if x not in (CONF_CONDITION_ID, key)), None)
if key2 is not None:
raise vol.Invalid(u"Cannot have two conditions in one item. Key '{}' overrides '{}'! "
u"Did you forget to indent the block inside the condition?"
u"".format(key, key2), path)
validator = CONDITION_REGISTRY[key][0]
try:
condition = validator(item[key] or {})
except vol.Invalid as err:
err.prepend(path)
raise err
value[i] = {
CONF_CONDITION_ID: cv.declare_variable_id(Condition)(item[CONF_CONDITION_ID]),
key: condition,
}
return value
def validate_recursive_action(value):
is_list = isinstance(value, list)
if not is_list:
value = [value]
for i, item in enumerate(value):
path = [i] if is_list else []
item = copy.deepcopy(item)
if not isinstance(item, dict):
raise vol.Invalid(u"Action must consist of key-value mapping! Got {}".format(item),
path)
key = next((x for x in item if x != CONF_ACTION_ID), None)
if key is None:
raise vol.Invalid(u"Key missing from action! Got {}".format(item), path)
if key not in ACTION_REGISTRY:
raise vol.Invalid(u"Unable to find action with the name '{}', is the component loaded?"
u"".format(key), path + [key])
item.setdefault(CONF_ACTION_ID, None)
key2 = next((x for x in item if x not in (CONF_ACTION_ID, key)), None)
if key2 is not None:
raise vol.Invalid(u"Cannot have two actions in one item. Key '{}' overrides '{}'! "
u"Did you forget to indent the block inside the action?"
u"".format(key, key2), path)
validator = ACTION_REGISTRY[key][0]
try:
action = validator(item[key] or {})
except vol.Invalid as err:
err.prepend(path)
raise err
value[i] = {
CONF_ACTION_ID: cv.declare_variable_id(Action)(item[CONF_ACTION_ID]),
key: action,
}
return value
ACTION_REGISTRY = ServiceRegistry()
CONDITION_REGISTRY = ServiceRegistry()
# pylint: disable=invalid-name
DelayAction = esphome_ns.class_('DelayAction', Action, Component)
LambdaAction = esphome_ns.class_('LambdaAction', Action)
IfAction = esphome_ns.class_('IfAction', Action)
WhileAction = esphome_ns.class_('WhileAction', Action)
WaitUntilAction = esphome_ns.class_('WaitUntilAction', Action, Component)
UpdateComponentAction = esphome_ns.class_('UpdateComponentAction', Action)
Automation = esphome_ns.class_('Automation')
Condition = esphome_ns.class_('Condition')
AndCondition = esphome_ns.class_('AndCondition', Condition)
OrCondition = esphome_ns.class_('OrCondition', Condition)
RangeCondition = esphome_ns.class_('RangeCondition', Condition)
LambdaCondition = esphome_ns.class_('LambdaCondition', Condition)
def validate_automation(extra_schema=None, extra_validators=None, single=False):
if extra_schema is None:
extra_schema = {}
if isinstance(extra_schema, vol.Schema):
extra_schema = extra_schema.schema
schema = AUTOMATION_SCHEMA.extend(extra_schema)
def validator_(value):
if isinstance(value, list):
try:
# First try as a sequence of actions
return [schema({CONF_THEN: value})]
except vol.Invalid as err:
if err.path and err.path[0] == CONF_THEN:
err.path.pop(0)
# Next try as a sequence of automations
try:
return cv.Schema([schema])(value)
except vol.Invalid as err2:
if 'Unable to find action' in str(err):
raise err2
raise vol.MultipleInvalid([err, err2])
elif isinstance(value, dict):
if CONF_THEN in value:
return [schema(value)]
return [schema({CONF_THEN: value})]
# This should only happen with invalid configs, but let's have a nice error message.
return [schema(value)]
def validator(value):
value = validator_(value)
if extra_validators is not None:
value = cv.Schema([extra_validators])(value)
if single:
if len(value) != 1:
raise vol.Invalid("Cannot have more than 1 automation for templates")
return value[0]
return value
return validator
AUTOMATION_SCHEMA = cv.Schema({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(Trigger),
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_variable_id(Automation),
vol.Required(CONF_THEN): validate_recursive_action,
})
AND_CONDITION_SCHEMA = validate_recursive_condition
@CONDITION_REGISTRY.register(CONF_AND, AND_CONDITION_SCHEMA)
def and_condition_to_code(config, condition_id, template_arg, args):
for conditions in build_conditions(config, template_arg, args):
yield
rhs = AndCondition.new(template_arg, conditions)
type = AndCondition.template(template_arg)
yield Pvariable(condition_id, rhs, type=type)
OR_CONDITION_SCHEMA = validate_recursive_condition
@CONDITION_REGISTRY.register(CONF_OR, OR_CONDITION_SCHEMA)
def or_condition_to_code(config, condition_id, template_arg, args):
for conditions in build_conditions(config, template_arg, args):
yield
rhs = OrCondition.new(template_arg, conditions)
type = OrCondition.template(template_arg)
yield Pvariable(condition_id, rhs, type=type)
RANGE_CONDITION_SCHEMA = vol.All(cv.Schema({
vol.Optional(CONF_ABOVE): cv.templatable(cv.float_),
vol.Optional(CONF_BELOW): cv.templatable(cv.float_),
}), cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW))
@CONDITION_REGISTRY.register(CONF_RANGE, RANGE_CONDITION_SCHEMA)
def range_condition_to_code(config, condition_id, template_arg, args):
for conditions in build_conditions(config, template_arg, args):
yield
rhs = RangeCondition.new(template_arg, conditions)
type = RangeCondition.template(template_arg)
condition = Pvariable(condition_id, rhs, type=type)
if CONF_ABOVE in config:
for template_ in templatable(config[CONF_ABOVE], args, float_):
yield
condition.set_min(template_)
if CONF_BELOW in config:
for template_ in templatable(config[CONF_BELOW], args, float_):
yield
condition.set_max(template_)
yield condition
DELAY_ACTION_SCHEMA = cv.templatable(cv.positive_time_period_milliseconds)
@ACTION_REGISTRY.register(CONF_DELAY, DELAY_ACTION_SCHEMA)
def delay_action_to_code(config, action_id, template_arg, args):
rhs = App.register_component(DelayAction.new(template_arg))
type = DelayAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
for template_ in templatable(config, args, uint32):
yield
add(action.set_delay(template_))
yield action
IF_ACTION_SCHEMA = vol.All({
vol.Required(CONF_CONDITION): validate_recursive_condition,
vol.Optional(CONF_THEN): validate_recursive_action,
vol.Optional(CONF_ELSE): validate_recursive_action,
}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE))
@ACTION_REGISTRY.register(CONF_IF, IF_ACTION_SCHEMA)
def if_action_to_code(config, action_id, template_arg, args):
for conditions in build_conditions(config[CONF_CONDITION], template_arg, args):
yield None
rhs = IfAction.new(template_arg, conditions)
type = IfAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
if CONF_THEN in config:
for actions in build_actions(config[CONF_THEN], template_arg, args):
yield None
add(action.add_then(actions))
if CONF_ELSE in config:
for actions in build_actions(config[CONF_ELSE], template_arg, args):
yield None
add(action.add_else(actions))
yield action
WHILE_ACTION_SCHEMA = cv.Schema({
vol.Required(CONF_CONDITION): validate_recursive_condition,
vol.Required(CONF_THEN): validate_recursive_action,
})
@ACTION_REGISTRY.register(CONF_WHILE, WHILE_ACTION_SCHEMA)
def while_action_to_code(config, action_id, template_arg, args):
for conditions in build_conditions(config[CONF_CONDITION], template_arg, args):
yield None
rhs = WhileAction.new(template_arg, conditions)
type = WhileAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
for actions in build_actions(config[CONF_THEN], template_arg, args):
yield None
add(action.add_then(actions))
yield action
def validate_wait_until(value):
schema = cv.Schema({
vol.Required(CONF_CONDITION): validate_recursive_condition
})
if isinstance(value, dict) and CONF_CONDITION in value:
return schema(value)
return validate_wait_until({CONF_CONDITION: value})
WAIT_UNTIL_ACTION_SCHEMA = validate_wait_until
@ACTION_REGISTRY.register(CONF_WAIT_UNTIL, WAIT_UNTIL_ACTION_SCHEMA)
def wait_until_action_to_code(config, action_id, template_arg, args):
for conditions in build_conditions(config[CONF_CONDITION], template_arg, args):
yield None
rhs = WaitUntilAction.new(template_arg, conditions)
type = WaitUntilAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
add(App.register_component(action))
yield action
LAMBDA_ACTION_SCHEMA = cv.lambda_
@ACTION_REGISTRY.register(CONF_LAMBDA, LAMBDA_ACTION_SCHEMA)
def lambda_action_to_code(config, action_id, template_arg, args):
for lambda_ in process_lambda(config, args, return_type=void):
yield None
rhs = LambdaAction.new(template_arg, lambda_)
type = LambdaAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
LAMBDA_CONDITION_SCHEMA = cv.lambda_
@CONDITION_REGISTRY.register(CONF_LAMBDA, LAMBDA_CONDITION_SCHEMA)
def lambda_condition_to_code(config, condition_id, template_arg, args):
for lambda_ in process_lambda(config, args, return_type=bool_):
yield
rhs = LambdaCondition.new(template_arg, lambda_)
type = LambdaCondition.template(template_arg)
yield Pvariable(condition_id, rhs, type=type)
CONF_COMPONENT_UPDATE = 'component.update'
COMPONENT_UPDATE_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(PollingComponent),
})
@ACTION_REGISTRY.register(CONF_COMPONENT_UPDATE, COMPONENT_UPDATE_ACTION_SCHEMA)
def component_update_action_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = UpdateComponentAction.new(template_arg, var)
type = UpdateComponentAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
def build_action(full_config, template_arg, args):
action_id = full_config[CONF_ACTION_ID]
key, config = next((k, v) for k, v in full_config.items() if k in ACTION_REGISTRY)
builder = ACTION_REGISTRY[key][1]
for result in builder(config, action_id, template_arg, args):
yield None
yield result
def build_actions(config, templ, arg_type):
actions = []
for conf in config:
for action in build_action(conf, templ, arg_type):
yield None
actions.append(action)
yield actions
def build_condition(full_config, template_arg, args):
action_id = full_config[CONF_CONDITION_ID]
key, config = next((k, v) for k, v in full_config.items() if k in CONDITION_REGISTRY)
builder = CONDITION_REGISTRY[key][1]
for result in builder(config, action_id, template_arg, args):
yield None
yield result
def build_conditions(config, templ, args):
conditions = []
for conf in config:
for condition in build_condition(conf, templ, args):
yield None
conditions.append(condition)
yield conditions
def build_automation_(trigger, args, config):
arg_types = [arg[0] for arg in args]
templ = TemplateArguments(*arg_types)
rhs = App.make_automation(templ, trigger)
type = Automation.template(templ)
obj = Pvariable(config[CONF_AUTOMATION_ID], rhs, type=type)
for actions in build_actions(config[CONF_THEN], templ, args):
yield None
add(obj.add_actions(actions))
yield obj
def build_automations(trigger, args, config):
CORE.add_job(build_automation_, trigger, args, config)

View File

View File

@@ -0,0 +1,27 @@
import voluptuous as vol
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID
from esphome.cpp_generator import Pvariable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Component
DEPENDENCIES = ['i2c']
MULTI_CONF = True
ADS1115Component = sensor.sensor_ns.class_('ADS1115Component', Component, i2c.I2CDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(ADS1115Component),
vol.Required(CONF_ADDRESS): cv.i2c_address,
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.make_ads1115_component(config[CONF_ADDRESS])
var = Pvariable(config[CONF_ID], rhs)
setup_component(var, config)
BUILD_FLAGS = '-DUSE_ADS1115_SENSOR'

View File

@@ -0,0 +1,33 @@
import voluptuous as vol
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_UPDATE_INTERVAL
from esphome.cpp_generator import Pvariable, add
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, PollingComponent
DEPENDENCIES = ['i2c']
MULTI_CONF = True
CONF_APDS9960_ID = 'apds9960_id'
APDS9960 = sensor.sensor_ns.class_('APDS9960', PollingComponent, i2c.I2CDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(APDS9960),
vol.Optional(CONF_ADDRESS): cv.i2c_address,
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.make_apds9960(config.get(CONF_UPDATE_INTERVAL))
var = Pvariable(config[CONF_ID], rhs)
if CONF_ADDRESS in config:
add(var.set_address(config[CONF_ADDRESS]))
setup_component(var, config)
BUILD_FLAGS = '-DUSE_APDS9960'

141
esphome/components/api.py Normal file
View File

@@ -0,0 +1,141 @@
import voluptuous as vol
from esphome import automation
from esphome.automation import ACTION_REGISTRY, CONDITION_REGISTRY, Condition
import esphome.config_validation as cv
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
from esphome.core import CORE
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import Action, App, Component, StoringController, esphome_ns, Trigger, \
bool_, int32, float_, std_string
api_ns = esphome_ns.namespace('api')
APIServer = api_ns.class_('APIServer', Component, StoringController)
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', Action)
KeyValuePair = api_ns.class_('KeyValuePair')
TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair')
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
UserService = api_ns.class_('UserService', Trigger)
ServiceTypeArgument = api_ns.class_('ServiceTypeArgument')
ServiceArgType = api_ns.enum('ServiceArgType')
SERVICE_ARG_TYPES = {
'bool': ServiceArgType.SERVICE_ARG_TYPE_BOOL,
'int': ServiceArgType.SERVICE_ARG_TYPE_INT,
'float': ServiceArgType.SERVICE_ARG_TYPE_FLOAT,
'string': ServiceArgType.SERVICE_ARG_TYPE_STRING,
}
SERVICE_ARG_NATIVE_TYPES = {
'bool': bool_,
'int': int32,
'float': float_,
'string': std_string,
}
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(APIServer),
vol.Optional(CONF_PORT, default=6053): cv.port,
vol.Optional(CONF_PASSWORD, default=''): cv.string_strict,
vol.Optional(CONF_REBOOT_TIMEOUT): cv.positive_time_period_milliseconds,
vol.Optional(CONF_SERVICES): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(UserService),
vol.Required(CONF_SERVICE): cv.valid_name,
vol.Optional(CONF_VARIABLES, default={}): cv.Schema({
cv.validate_id_name: cv.one_of(*SERVICE_ARG_TYPES, lower=True),
}),
}),
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.init_api_server()
api = Pvariable(config[CONF_ID], rhs)
if config[CONF_PORT] != 6053:
add(api.set_port(config[CONF_PORT]))
if config.get(CONF_PASSWORD):
add(api.set_password(config[CONF_PASSWORD]))
if CONF_REBOOT_TIMEOUT in config:
add(api.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
for conf in config.get(CONF_SERVICES, []):
template_args = []
func_args = []
service_type_args = []
for name, var_ in conf[CONF_VARIABLES].items():
native = SERVICE_ARG_NATIVE_TYPES[var_]
template_args.append(native)
func_args.append((native, name))
service_type_args.append(ServiceTypeArgument(name, SERVICE_ARG_TYPES[var_]))
func = api.make_user_service_trigger.template(*template_args)
rhs = func(conf[CONF_SERVICE], service_type_args)
type_ = UserService.template(*template_args)
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs, type=type_)
automation.build_automations(trigger, func_args, conf)
setup_component(api, config)
BUILD_FLAGS = '-DUSE_API'
def lib_deps(config):
if CORE.is_esp32:
return 'AsyncTCP@1.0.3'
if CORE.is_esp8266:
return 'ESPAsyncTCP@1.1.3'
raise NotImplementedError
CONF_HOMEASSISTANT_SERVICE = 'homeassistant.service'
HOMEASSISTANT_SERVIC_ACTION_SCHEMA = cv.Schema({
cv.GenerateID(): cv.use_variable_id(APIServer),
vol.Required(CONF_SERVICE): cv.string,
vol.Optional(CONF_DATA): cv.Schema({
cv.string: cv.string,
}),
vol.Optional(CONF_DATA_TEMPLATE): cv.Schema({
cv.string: cv.string,
}),
vol.Optional(CONF_VARIABLES): cv.Schema({
cv.string: cv.lambda_,
}),
})
@ACTION_REGISTRY.register(CONF_HOMEASSISTANT_SERVICE, HOMEASSISTANT_SERVIC_ACTION_SCHEMA)
def homeassistant_service_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_home_assistant_service_call_action(template_arg)
type = HomeAssistantServiceCallAction.template(template_arg)
act = Pvariable(action_id, rhs, type=type)
add(act.set_service(config[CONF_SERVICE]))
if CONF_DATA in config:
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA].items()]
add(act.set_data(datas))
if CONF_DATA_TEMPLATE in config:
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA_TEMPLATE].items()]
add(act.set_data_template(datas))
if CONF_VARIABLES in config:
datas = []
for key, value in config[CONF_VARIABLES].items():
for value_ in process_lambda(value, []):
yield None
datas.append(TemplatableKeyValuePair(key, value_))
add(act.set_variables(datas))
yield act
CONF_API_CONNECTED = 'api.connected'
API_CONNECTED_CONDITION_SCHEMA = cv.Schema({})
@CONDITION_REGISTRY.register(CONF_API_CONNECTED, API_CONNECTED_CONDITION_SCHEMA)
def api_connected_to_code(config, condition_id, template_arg, args):
rhs = APIConnectedCondition.new(template_arg)
type = APIConnectedCondition.template(template_arg)
yield Pvariable(condition_id, rhs, type=type)

View File

@@ -0,0 +1,331 @@
import voluptuous as vol
from esphome import automation, core
from esphome.automation import CONDITION_REGISTRY, Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.components.mqtt import setup_mqtt_component
import esphome.config_validation as cv
from esphome.const import CONF_DELAYED_OFF, CONF_DELAYED_ON, CONF_DEVICE_CLASS, CONF_FILTERS, \
CONF_HEARTBEAT, CONF_ID, CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERT, CONF_INVERTED, \
CONF_LAMBDA, CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_MQTT_ID, 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
from esphome.core import CORE
from esphome.cpp_generator import Pvariable, StructInitializer, add, get_variable, process_lambda
from esphome.cpp_types import App, Component, Nameable, Trigger, bool_, esphome_ns, optional
from esphome.py_compat import string_types
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'
]
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
})
binary_sensor_ns = esphome_ns.namespace('binary_sensor')
BinarySensor = binary_sensor_ns.class_('BinarySensor', Nameable)
BinarySensorPtr = BinarySensor.operator('ptr')
MQTTBinarySensorComponent = binary_sensor_ns.class_('MQTTBinarySensorComponent', mqtt.MQTTComponent)
# Triggers
PressTrigger = binary_sensor_ns.class_('PressTrigger', Trigger.template())
ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', Trigger.template())
ClickTrigger = binary_sensor_ns.class_('ClickTrigger', Trigger.template())
DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', Trigger.template())
MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', Trigger.template(), Component)
MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent')
StateTrigger = binary_sensor_ns.class_('StateTrigger', Trigger.template(bool_))
# Condition
BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition)
# Filters
Filter = binary_sensor_ns.class_('Filter')
DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, Component)
DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, Component)
HeartbeatFilter = binary_sensor_ns.class_('HeartbeatFilter', Filter, Component)
InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter)
LambdaFilter = binary_sensor_ns.class_('LambdaFilter', Filter)
FILTER_KEYS = [CONF_INVERT, CONF_DELAYED_ON, CONF_DELAYED_OFF, CONF_LAMBDA, CONF_HEARTBEAT]
FILTERS_SCHEMA = cv.ensure_list({
vol.Optional(CONF_INVERT): None,
vol.Optional(CONF_DELAYED_ON): cv.positive_time_period_milliseconds,
vol.Optional(CONF_DELAYED_OFF): cv.positive_time_period_milliseconds,
vol.Optional(CONF_LAMBDA): cv.lambda_,
vol.Optional(CONF_HEARTBEAT): cv.invalid("The heartbeat filter has been removed in 1.11.0"),
}, cv.has_exactly_one_key(*FILTER_KEYS))
MULTI_CLICK_TIMING_SCHEMA = cv.Schema({
vol.Optional(CONF_STATE): cv.boolean,
vol.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds,
})
def parse_multi_click_timing_str(value):
if not isinstance(value, string_types):
return value
parts = value.lower().split(' ')
if len(parts) != 5:
raise vol.Invalid("Multi click timing grammar consists of exactly 5 words, not {}"
"".format(len(parts)))
try:
state = cv.boolean(parts[0])
except vol.Invalid:
raise vol.Invalid(u"First word must either be ON or OFF, not {}".format(parts[0]))
if parts[1] != 'for':
raise vol.Invalid(u"Second word must be 'for', got {}".format(parts[1]))
if parts[2] == 'at':
if parts[3] == 'least':
key = CONF_MIN_LENGTH
elif parts[3] == 'most':
key = CONF_MAX_LENGTH
else:
raise vol.Invalid(u"Third word after at must either be 'least' or 'most', got {}"
u"".format(parts[3]))
try:
length = cv.positive_time_period_milliseconds(parts[4])
except vol.Invalid as err:
raise vol.Invalid(u"Multi Click Grammar Parsing length failed: {}".format(err))
return {
CONF_STATE: state,
key: str(length)
}
if parts[3] != 'to':
raise vol.Invalid("Multi click grammar: 4th word must be 'to'")
try:
min_length = cv.positive_time_period_milliseconds(parts[2])
except vol.Invalid as err:
raise vol.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err))
try:
max_length = cv.positive_time_period_milliseconds(parts[4])
except vol.Invalid as err:
raise vol.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err))
return {
CONF_STATE: state,
CONF_MIN_LENGTH: str(min_length),
CONF_MAX_LENGTH: str(max_length)
}
def validate_multi_click_timing(value):
if not isinstance(value, list):
raise vol.Invalid("Timing option must be a *list* of times!")
timings = []
state = None
for i, v_ in enumerate(value):
v_ = MULTI_CLICK_TIMING_SCHEMA(v_)
min_length = v_.get(CONF_MIN_LENGTH)
max_length = v_.get(CONF_MAX_LENGTH)
if min_length is None and max_length is None:
raise vol.Invalid("At least one of min_length and max_length is required!")
if min_length is None and max_length is not None:
min_length = core.TimePeriodMilliseconds(milliseconds=0)
new_state = v_.get(CONF_STATE, not state)
if new_state == state:
raise vol.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 vol.Invalid("Max length ({}) must be larger than min length ({})."
"".format(max_length, min_length))
state = new_state
tim = {
CONF_STATE: new_state,
CONF_MIN_LENGTH: min_length,
}
if max_length is not None:
tim[CONF_MAX_LENGTH] = max_length
timings.append(tim)
return timings
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTBinarySensorComponent),
vol.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
vol.Optional(CONF_FILTERS): FILTERS_SCHEMA,
vol.Optional(CONF_ON_PRESS): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(PressTrigger),
}),
vol.Optional(CONF_ON_RELEASE): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ReleaseTrigger),
}),
vol.Optional(CONF_ON_CLICK): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ClickTrigger),
vol.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
}),
vol.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(DoubleClickTrigger),
vol.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
}),
vol.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MultiClickTrigger),
vol.Required(CONF_TIMING): vol.All([parse_multi_click_timing_str],
validate_multi_click_timing),
vol.Optional(CONF_INVALID_COOLDOWN): cv.positive_time_period_milliseconds,
}),
vol.Optional(CONF_ON_STATE): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(StateTrigger),
}),
vol.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."
),
})
BINARY_SENSOR_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(BINARY_SENSOR_SCHEMA.schema)
def setup_filter(config):
if CONF_INVERT in config:
yield InvertFilter.new()
elif CONF_DELAYED_OFF in config:
yield App.register_component(DelayedOffFilter.new(config[CONF_DELAYED_OFF]))
elif CONF_DELAYED_ON in config:
yield App.register_component(DelayedOnFilter.new(config[CONF_DELAYED_ON]))
elif CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA], [(bool_, 'x')],
return_type=optional.template(bool_)):
yield None
yield LambdaFilter.new(lambda_)
def setup_filters(config):
filters = []
for conf in config:
for filter in setup_filter(conf):
yield None
filters.append(filter)
yield filters
def setup_binary_sensor_core_(binary_sensor_var, config):
if CONF_INTERNAL in config:
add(binary_sensor_var.set_internal(CONF_INTERNAL))
if CONF_DEVICE_CLASS in config:
add(binary_sensor_var.set_device_class(config[CONF_DEVICE_CLASS]))
if CONF_INVERTED in config:
add(binary_sensor_var.set_inverted(config[CONF_INVERTED]))
if CONF_FILTERS in config:
for filters in setup_filters(config[CONF_FILTERS]):
yield
add(binary_sensor_var.add_filters(filters))
for conf in config.get(CONF_ON_PRESS, []):
rhs = binary_sensor_var.make_press_trigger()
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
automation.build_automations(trigger, [], conf)
for conf in config.get(CONF_ON_RELEASE, []):
rhs = binary_sensor_var.make_release_trigger()
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
automation.build_automations(trigger, [], conf)
for conf in config.get(CONF_ON_CLICK, []):
rhs = binary_sensor_var.make_click_trigger(conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH])
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
automation.build_automations(trigger, [], conf)
for conf in config.get(CONF_ON_DOUBLE_CLICK, []):
rhs = binary_sensor_var.make_double_click_trigger(conf[CONF_MIN_LENGTH],
conf[CONF_MAX_LENGTH])
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
automation.build_automations(trigger, [], conf)
for conf in config.get(CONF_ON_MULTI_CLICK, []):
timings = []
for tim in conf[CONF_TIMING]:
timings.append(StructInitializer(
MultiClickTriggerEvent,
('state', tim[CONF_STATE]),
('min_length', tim[CONF_MIN_LENGTH]),
('max_length', tim.get(CONF_MAX_LENGTH, 4294967294)),
))
rhs = App.register_component(binary_sensor_var.make_multi_click_trigger(timings))
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
if CONF_INVALID_COOLDOWN in conf:
add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
automation.build_automations(trigger, [], conf)
for conf in config.get(CONF_ON_STATE, []):
rhs = binary_sensor_var.make_state_trigger()
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
automation.build_automations(trigger, [(bool_, 'x')], conf)
setup_mqtt_component(binary_sensor_var.Pget_mqtt(), config)
def setup_binary_sensor(binary_sensor_obj, config):
if not CORE.has_id(config[CONF_ID]):
binary_sensor_obj = Pvariable(config[CONF_ID], binary_sensor_obj, has_side_effects=True)
CORE.add_job(setup_binary_sensor_core_, binary_sensor_obj, config)
def register_binary_sensor(var, config):
binary_sensor_var = Pvariable(config[CONF_ID], var, has_side_effects=True)
add(App.register_binary_sensor(binary_sensor_var))
CORE.add_job(setup_binary_sensor_core_, binary_sensor_var, config)
def core_to_hass_config(data, config):
ret = mqtt.build_hass_config(data, 'binary_sensor', config,
include_state=True, include_command=False)
if ret is None:
return None
if CONF_DEVICE_CLASS in config:
ret['device_class'] = config[CONF_DEVICE_CLASS]
return ret
BUILD_FLAGS = '-DUSE_BINARY_SENSOR'
CONF_BINARY_SENSOR_IS_ON = 'binary_sensor.is_on'
BINARY_SENSOR_IS_ON_CONDITION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(BinarySensor),
vol.Optional(CONF_FOR): cv.positive_time_period_milliseconds,
})
@CONDITION_REGISTRY.register(CONF_BINARY_SENSOR_IS_ON, BINARY_SENSOR_IS_ON_CONDITION_SCHEMA)
def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_binary_sensor_is_on_condition(template_arg, config.get(CONF_FOR))
type = BinarySensorCondition.template(template_arg)
yield Pvariable(condition_id, rhs, type=type)
CONF_BINARY_SENSOR_IS_OFF = 'binary_sensor.is_off'
BINARY_SENSOR_IS_OFF_CONDITION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(BinarySensor),
vol.Optional(CONF_FOR): cv.positive_time_period_milliseconds,
})
@CONDITION_REGISTRY.register(CONF_BINARY_SENSOR_IS_OFF, BINARY_SENSOR_IS_OFF_CONDITION_SCHEMA)
def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_binary_sensor_is_off_condition(template_arg, config.get(CONF_FOR))
type = BinarySensorCondition.template(template_arg)
yield Pvariable(condition_id, rhs, type=type)

View File

@@ -0,0 +1,36 @@
import voluptuous as vol
from esphome.components import binary_sensor, sensor
from esphome.components.apds9960 import APDS9960, CONF_APDS9960_ID
import esphome.config_validation as cv
from esphome.const import CONF_DIRECTION, CONF_NAME
from esphome.cpp_generator import get_variable
DEPENDENCIES = ['apds9960']
APDS9960GestureDirectionBinarySensor = sensor.sensor_ns.class_(
'APDS9960GestureDirectionBinarySensor', binary_sensor.BinarySensor)
DIRECTIONS = {
'UP': 'make_up_direction',
'DOWN': 'make_down_direction',
'LEFT': 'make_left_direction',
'RIGHT': 'make_right_direction',
}
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(APDS9960GestureDirectionBinarySensor),
vol.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
cv.GenerateID(CONF_APDS9960_ID): cv.use_variable_id(APDS9960)
}))
def to_code(config):
for hub in get_variable(config[CONF_APDS9960_ID]):
yield
func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
rhs = func(config[CONF_NAME])
binary_sensor.register_binary_sensor(rhs, config)
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,39 @@
import voluptuous as vol
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA, CONF_NAME
from esphome.cpp_generator import add, process_lambda, variable
from esphome.cpp_types import std_vector
CustomBinarySensorConstructor = binary_sensor.binary_sensor_ns.class_(
'CustomBinarySensorConstructor')
PLATFORM_SCHEMA = binary_sensor.PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(CustomBinarySensorConstructor),
vol.Required(CONF_LAMBDA): cv.lambda_,
vol.Required(CONF_BINARY_SENSORS):
cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(binary_sensor.BinarySensor),
})),
})
def to_code(config):
for template_ in process_lambda(config[CONF_LAMBDA], [],
return_type=std_vector.template(binary_sensor.BinarySensorPtr)):
yield
rhs = CustomBinarySensorConstructor(template_)
custom = variable(config[CONF_ID], rhs)
for i, conf in enumerate(config[CONF_BINARY_SENSORS]):
rhs = custom.Pget_binary_sensor(i)
add(rhs.set_name(conf[CONF_NAME]))
binary_sensor.register_binary_sensor(rhs, conf)
BUILD_FLAGS = '-DUSE_CUSTOM_BINARY_SENSOR'
def to_hass_config(data, config):
return [binary_sensor.core_to_hass_config(data, sens) for sens in config[CONF_BINARY_SENSORS]]

View File

@@ -0,0 +1,29 @@
import voluptuous as vol
from esphome.components import binary_sensor
from esphome.components.esp32_ble_tracker import CONF_ESP32_BLE_ID, ESP32BLETracker, \
make_address_array
import esphome.config_validation as cv
from esphome.const import CONF_MAC_ADDRESS, CONF_NAME
from esphome.cpp_generator import get_variable
from esphome.cpp_types import esphome_ns
DEPENDENCIES = ['esp32_ble_tracker']
ESP32BLEPresenceDevice = esphome_ns.class_('ESP32BLEPresenceDevice', binary_sensor.BinarySensor)
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(ESP32BLEPresenceDevice),
vol.Required(CONF_MAC_ADDRESS): cv.mac_address,
cv.GenerateID(CONF_ESP32_BLE_ID): cv.use_variable_id(ESP32BLETracker)
}))
def to_code(config):
for hub in get_variable(config[CONF_ESP32_BLE_ID]):
yield
rhs = hub.make_presence_sensor(config[CONF_NAME], make_address_array(config[CONF_MAC_ADDRESS]))
binary_sensor.register_binary_sensor(rhs, config)
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,62 @@
import voluptuous as vol
from esphome.components import binary_sensor
from esphome.components.esp32_touch import ESP32TouchComponent
import esphome.config_validation as cv
from esphome.const import CONF_NAME, CONF_PIN, CONF_THRESHOLD, ESP_PLATFORM_ESP32
from esphome.cpp_generator import get_variable
from esphome.cpp_types import global_ns
from esphome.pins import validate_gpio_pin
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
DEPENDENCIES = ['esp32_touch']
CONF_ESP32_TOUCH_ID = 'esp32_touch_id'
TOUCH_PADS = {
4: global_ns.TOUCH_PAD_NUM0,
0: global_ns.TOUCH_PAD_NUM1,
2: global_ns.TOUCH_PAD_NUM2,
15: global_ns.TOUCH_PAD_NUM3,
13: global_ns.TOUCH_PAD_NUM4,
12: global_ns.TOUCH_PAD_NUM5,
14: global_ns.TOUCH_PAD_NUM6,
27: global_ns.TOUCH_PAD_NUM7,
33: global_ns.TOUCH_PAD_NUM8,
32: global_ns.TOUCH_PAD_NUM9,
}
def validate_touch_pad(value):
value = validate_gpio_pin(value)
if value not in TOUCH_PADS:
raise vol.Invalid("Pin {} does not support touch pads.".format(value))
return value
ESP32TouchBinarySensor = binary_sensor.binary_sensor_ns.class_('ESP32TouchBinarySensor',
binary_sensor.BinarySensor)
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(ESP32TouchBinarySensor),
vol.Required(CONF_PIN): validate_touch_pad,
vol.Required(CONF_THRESHOLD): cv.uint16_t,
cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_variable_id(ESP32TouchComponent),
}))
def to_code(config):
hub = None
for hub in get_variable(config[CONF_ESP32_TOUCH_ID]):
yield
touch_pad = TOUCH_PADS[config[CONF_PIN]]
rhs = hub.make_touch_pad(config[CONF_NAME], touch_pad, config[CONF_THRESHOLD])
binary_sensor.register_binary_sensor(rhs, config)
BUILD_FLAGS = '-DUSE_ESP32_TOUCH_BINARY_SENSOR'
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,35 @@
import voluptuous as vol
from esphome import pins
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NAME, CONF_PIN
from esphome.cpp_generator import Pvariable
from esphome.cpp_helpers import gpio_input_pin_expression, setup_component
from esphome.cpp_types import App, Component
GPIOBinarySensorComponent = binary_sensor.binary_sensor_ns.class_('GPIOBinarySensorComponent',
binary_sensor.BinarySensor,
Component)
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(GPIOBinarySensorComponent),
vol.Required(CONF_PIN): pins.gpio_input_pin_schema
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
pin = None
for pin in gpio_input_pin_expression(config[CONF_PIN]):
yield
rhs = App.make_gpio_binary_sensor(config[CONF_NAME], pin)
gpio = Pvariable(config[CONF_ID], rhs)
binary_sensor.setup_binary_sensor(gpio, config)
setup_component(gpio, config)
BUILD_FLAGS = '-DUSE_GPIO_BINARY_SENSOR'
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,30 @@
import voluptuous as vol
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ENTITY_ID, CONF_ID, CONF_NAME
from esphome.cpp_generator import Pvariable
from esphome.cpp_types import App
DEPENDENCIES = ['api']
HomeassistantBinarySensor = binary_sensor.binary_sensor_ns.class_('HomeassistantBinarySensor',
binary_sensor.BinarySensor)
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(HomeassistantBinarySensor),
vol.Required(CONF_ENTITY_ID): cv.entity_id,
}))
def to_code(config):
rhs = App.make_homeassistant_binary_sensor(config[CONF_NAME], config[CONF_ENTITY_ID])
subs = Pvariable(config[CONF_ID], rhs)
binary_sensor.setup_binary_sensor(subs, config)
BUILD_FLAGS = '-DUSE_HOMEASSISTANT_BINARY_SENSOR'
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,28 @@
import voluptuous as vol
from esphome.components import binary_sensor
from esphome.components.mpr121 import MPR121Component, CONF_MPR121_ID
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_NAME
from esphome.cpp_generator import get_variable
DEPENDENCIES = ['mpr121']
MPR121Channel = binary_sensor.binary_sensor_ns.class_(
'MPR121Channel', binary_sensor.BinarySensor)
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(MPR121Channel),
cv.GenerateID(CONF_MPR121_ID): cv.use_variable_id(MPR121Component),
vol.Required(CONF_CHANNEL): vol.All(vol.Coerce(int), vol.Range(min=0, max=11))
}))
def to_code(config):
for hub in get_variable(config[CONF_MPR121_ID]):
yield
rhs = MPR121Channel.new(config[CONF_NAME], config[CONF_CHANNEL])
binary_sensor.register_binary_sensor(hub.add_channel(rhs), config)
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,33 @@
import voluptuous as vol
from esphome.components import binary_sensor, display
from esphome.components.display.nextion import Nextion
import esphome.config_validation as cv
from esphome.const import CONF_COMPONENT_ID, CONF_NAME, CONF_PAGE_ID
from esphome.cpp_generator import get_variable
DEPENDENCIES = ['display']
CONF_NEXTION_ID = 'nextion_id'
NextionTouchComponent = display.display_ns.class_('NextionTouchComponent',
binary_sensor.BinarySensor)
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(NextionTouchComponent),
vol.Required(CONF_PAGE_ID): cv.uint8_t,
vol.Required(CONF_COMPONENT_ID): cv.uint8_t,
cv.GenerateID(CONF_NEXTION_ID): cv.use_variable_id(Nextion)
}))
def to_code(config):
for hub in get_variable(config[CONF_NEXTION_ID]):
yield
rhs = hub.make_touch_component(config[CONF_NAME], config[CONF_PAGE_ID],
config[CONF_COMPONENT_ID])
binary_sensor.register_binary_sensor(rhs, config)
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,49 @@
import voluptuous as vol
from esphome.components import binary_sensor
from esphome.components.pn532 import PN532Component
import esphome.config_validation as cv
from esphome.const import CONF_NAME, CONF_UID
from esphome.core import HexInt
from esphome.cpp_generator import get_variable
DEPENDENCIES = ['pn532']
CONF_PN532_ID = 'pn532_id'
def validate_uid(value):
value = cv.string_strict(value)
for x in value.split('-'):
if len(x) != 2:
raise vol.Invalid("Each part (separated by '-') of the UID must be two characters "
"long.")
try:
x = int(x, 16)
except ValueError:
raise vol.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.")
if x < 0 or x > 255:
raise vol.Invalid("Valid values for UID parts (separated by '-') are 00 to FF")
return value
PN532BinarySensor = binary_sensor.binary_sensor_ns.class_('PN532BinarySensor',
binary_sensor.BinarySensor)
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(PN532BinarySensor),
vol.Required(CONF_UID): validate_uid,
cv.GenerateID(CONF_PN532_ID): cv.use_variable_id(PN532Component)
}))
def to_code(config):
for hub in get_variable(config[CONF_PN532_ID]):
yield
addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')]
rhs = hub.make_tag(config[CONF_NAME], addr)
binary_sensor.register_binary_sensor(rhs, config)
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,30 @@
import voluptuous as vol
from esphome.components import binary_sensor, rdm6300
import esphome.config_validation as cv
from esphome.const import CONF_NAME, CONF_UID
from esphome.cpp_generator import get_variable
DEPENDENCIES = ['rdm6300']
CONF_RDM6300_ID = 'rdm6300_id'
RDM6300BinarySensor = binary_sensor.binary_sensor_ns.class_('RDM6300BinarySensor',
binary_sensor.BinarySensor)
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(RDM6300BinarySensor),
vol.Required(CONF_UID): cv.uint32_t,
cv.GenerateID(CONF_RDM6300_ID): cv.use_variable_id(rdm6300.RDM6300Component)
}))
def to_code(config):
for hub in get_variable(config[CONF_RDM6300_ID]):
yield
rhs = hub.make_card(config[CONF_NAME], config[CONF_UID])
binary_sensor.register_binary_sensor(rhs, config)
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,151 @@
import voluptuous as vol
from esphome.components import binary_sensor
from esphome.components.remote_receiver import RemoteReceiverComponent, remote_ns
from esphome.components.remote_transmitter import RC_SWITCH_RAW_SCHEMA, \
RC_SWITCH_TYPE_A_SCHEMA, RC_SWITCH_TYPE_B_SCHEMA, RC_SWITCH_TYPE_C_SCHEMA, \
RC_SWITCH_TYPE_D_SCHEMA, binary_code, build_rc_switch_protocol
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_CHANNEL, CONF_CODE, CONF_COMMAND, CONF_DATA, \
CONF_DEVICE, CONF_FAMILY, CONF_GROUP, CONF_ID, CONF_JVC, CONF_LG, CONF_NAME, CONF_NBITS, \
CONF_NEC, CONF_PANASONIC, CONF_PROTOCOL, CONF_RAW, CONF_RC_SWITCH_RAW, CONF_RC_SWITCH_TYPE_A, \
CONF_RC_SWITCH_TYPE_B, CONF_RC_SWITCH_TYPE_C, CONF_RC_SWITCH_TYPE_D, CONF_SAMSUNG, CONF_SONY, \
CONF_STATE, CONF_RC5
from esphome.cpp_generator import Pvariable, get_variable, progmem_array
from esphome.cpp_types import int32
DEPENDENCIES = ['remote_receiver']
REMOTE_KEYS = [CONF_JVC, CONF_NEC, CONF_LG, CONF_SONY, CONF_PANASONIC, CONF_SAMSUNG, CONF_RAW,
CONF_RC_SWITCH_RAW, CONF_RC_SWITCH_TYPE_A, CONF_RC_SWITCH_TYPE_B,
CONF_RC_SWITCH_TYPE_C, CONF_RC_SWITCH_TYPE_D, CONF_RC5]
CONF_REMOTE_RECEIVER_ID = 'remote_receiver_id'
CONF_RECEIVER_ID = 'receiver_id'
RemoteReceiver = remote_ns.class_('RemoteReceiver', binary_sensor.BinarySensor)
JVCReceiver = remote_ns.class_('JVCReceiver', RemoteReceiver)
LGReceiver = remote_ns.class_('LGReceiver', RemoteReceiver)
NECReceiver = remote_ns.class_('NECReceiver', RemoteReceiver)
PanasonicReceiver = remote_ns.class_('PanasonicReceiver', RemoteReceiver)
RawReceiver = remote_ns.class_('RawReceiver', RemoteReceiver)
SamsungReceiver = remote_ns.class_('SamsungReceiver', RemoteReceiver)
SonyReceiver = remote_ns.class_('SonyReceiver', RemoteReceiver)
RC5Receiver = remote_ns.class_('RC5Receiver', RemoteReceiver)
RCSwitchRawReceiver = remote_ns.class_('RCSwitchRawReceiver', RemoteReceiver)
RCSwitchTypeAReceiver = remote_ns.class_('RCSwitchTypeAReceiver', RCSwitchRawReceiver)
RCSwitchTypeBReceiver = remote_ns.class_('RCSwitchTypeBReceiver', RCSwitchRawReceiver)
RCSwitchTypeCReceiver = remote_ns.class_('RCSwitchTypeCReceiver', RCSwitchRawReceiver)
RCSwitchTypeDReceiver = remote_ns.class_('RCSwitchTypeDReceiver', RCSwitchRawReceiver)
def validate_raw(value):
if isinstance(value, dict):
return cv.Schema({
cv.GenerateID(): cv.declare_variable_id(int32),
vol.Required(CONF_DATA): [vol.Any(vol.Coerce(int), cv.time_period_microseconds)],
})(value)
return validate_raw({
CONF_DATA: value
})
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(RemoteReceiver),
vol.Optional(CONF_JVC): cv.Schema({
vol.Required(CONF_DATA): cv.hex_uint32_t,
}),
vol.Optional(CONF_LG): cv.Schema({
vol.Required(CONF_DATA): cv.hex_uint32_t,
vol.Optional(CONF_NBITS, default=28): cv.one_of(28, 32, int=True),
}),
vol.Optional(CONF_NEC): cv.Schema({
vol.Required(CONF_ADDRESS): cv.hex_uint16_t,
vol.Required(CONF_COMMAND): cv.hex_uint16_t,
}),
vol.Optional(CONF_SAMSUNG): cv.Schema({
vol.Required(CONF_DATA): cv.hex_uint32_t,
}),
vol.Optional(CONF_SONY): cv.Schema({
vol.Required(CONF_DATA): cv.hex_uint32_t,
vol.Optional(CONF_NBITS, default=12): cv.one_of(12, 15, 20, int=True),
}),
vol.Optional(CONF_PANASONIC): cv.Schema({
vol.Required(CONF_ADDRESS): cv.hex_uint16_t,
vol.Required(CONF_COMMAND): cv.hex_uint32_t,
}),
vol.Optional(CONF_RC5): cv.Schema({
vol.Required(CONF_ADDRESS): vol.All(cv.hex_int, vol.Range(min=0, max=0x1F)),
vol.Required(CONF_COMMAND): vol.All(cv.hex_int, vol.Range(min=0, max=0x3F)),
}),
vol.Optional(CONF_RAW): validate_raw,
vol.Optional(CONF_RC_SWITCH_RAW): RC_SWITCH_RAW_SCHEMA,
vol.Optional(CONF_RC_SWITCH_TYPE_A): RC_SWITCH_TYPE_A_SCHEMA,
vol.Optional(CONF_RC_SWITCH_TYPE_B): RC_SWITCH_TYPE_B_SCHEMA,
vol.Optional(CONF_RC_SWITCH_TYPE_C): RC_SWITCH_TYPE_C_SCHEMA,
vol.Optional(CONF_RC_SWITCH_TYPE_D): RC_SWITCH_TYPE_D_SCHEMA,
cv.GenerateID(CONF_REMOTE_RECEIVER_ID): cv.use_variable_id(RemoteReceiverComponent),
cv.GenerateID(CONF_RECEIVER_ID): cv.declare_variable_id(RemoteReceiver),
}), cv.has_exactly_one_key(*REMOTE_KEYS))
def receiver_base(full_config):
name = full_config[CONF_NAME]
key, config = next((k, v) for k, v in full_config.items() if k in REMOTE_KEYS)
if key == CONF_JVC:
return JVCReceiver.new(name, config[CONF_DATA])
if key == CONF_LG:
return LGReceiver.new(name, config[CONF_DATA], config[CONF_NBITS])
if key == CONF_NEC:
return NECReceiver.new(name, config[CONF_ADDRESS], config[CONF_COMMAND])
if key == CONF_PANASONIC:
return PanasonicReceiver.new(name, config[CONF_ADDRESS], config[CONF_COMMAND])
if key == CONF_SAMSUNG:
return SamsungReceiver.new(name, config[CONF_DATA])
if key == CONF_SONY:
return SonyReceiver.new(name, config[CONF_DATA], config[CONF_NBITS])
if key == CONF_RC5:
return RC5Receiver.new(name, config[CONF_ADDRESS], config[CONF_COMMAND])
if key == CONF_RAW:
arr = progmem_array(config[CONF_ID], config[CONF_DATA])
return RawReceiver.new(name, arr, len(config[CONF_DATA]))
if key == CONF_RC_SWITCH_RAW:
return RCSwitchRawReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
binary_code(config[CONF_CODE]), len(config[CONF_CODE]))
if key == CONF_RC_SWITCH_TYPE_A:
return RCSwitchTypeAReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
binary_code(config[CONF_GROUP]),
binary_code(config[CONF_DEVICE]),
config[CONF_STATE])
if key == CONF_RC_SWITCH_TYPE_B:
return RCSwitchTypeBReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
config[CONF_ADDRESS], config[CONF_CHANNEL],
config[CONF_STATE])
if key == CONF_RC_SWITCH_TYPE_C:
return RCSwitchTypeCReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
ord(config[CONF_FAMILY][0]) - ord('a'),
config[CONF_GROUP], config[CONF_DEVICE],
config[CONF_STATE])
if key == CONF_RC_SWITCH_TYPE_D:
return RCSwitchTypeDReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
ord(config[CONF_GROUP][0]) - ord('a'),
config[CONF_DEVICE], config[CONF_STATE])
raise NotImplementedError("Unknown receiver type {}".format(config))
def to_code(config):
for remote in get_variable(config[CONF_REMOTE_RECEIVER_ID]):
yield
rhs = receiver_base(config)
receiver = Pvariable(config[CONF_RECEIVER_ID], rhs)
binary_sensor.register_binary_sensor(remote.add_decoder(receiver), config)
BUILD_FLAGS = '-DUSE_REMOTE_RECEIVER'
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,28 @@
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NAME
from esphome.cpp_generator import Pvariable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Component
StatusBinarySensor = binary_sensor.binary_sensor_ns.class_('StatusBinarySensor',
binary_sensor.BinarySensor,
Component)
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(StatusBinarySensor),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
rhs = App.make_status_binary_sensor(config[CONF_NAME])
status = Pvariable(config[CONF_ID], rhs)
binary_sensor.setup_binary_sensor(status, config)
setup_component(status, config)
BUILD_FLAGS = '-DUSE_STATUS_BINARY_SENSOR'
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,60 @@
import voluptuous as vol
from esphome.automation import ACTION_REGISTRY
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_STATE
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda, templatable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import Action, App, Component, bool_, optional
TemplateBinarySensor = binary_sensor.binary_sensor_ns.class_('TemplateBinarySensor',
binary_sensor.BinarySensor,
Component)
BinarySensorPublishAction = binary_sensor.binary_sensor_ns.class_('BinarySensorPublishAction',
Action)
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(TemplateBinarySensor),
vol.Optional(CONF_LAMBDA): cv.lambda_,
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
rhs = App.make_template_binary_sensor(config[CONF_NAME])
var = Pvariable(config[CONF_ID], rhs)
binary_sensor.setup_binary_sensor(var, config)
setup_component(var, config)
if CONF_LAMBDA in config:
for template_ in process_lambda(config[CONF_LAMBDA], [],
return_type=optional.template(bool_)):
yield
add(var.set_template(template_))
BUILD_FLAGS = '-DUSE_TEMPLATE_BINARY_SENSOR'
CONF_BINARY_SENSOR_TEMPLATE_PUBLISH = 'binary_sensor.template.publish'
BINARY_SENSOR_TEMPLATE_PUBLISH_ACTION_SCHEMA = cv.Schema({
vol.Required(CONF_ID): cv.use_variable_id(binary_sensor.BinarySensor),
vol.Required(CONF_STATE): cv.templatable(cv.boolean),
})
@ACTION_REGISTRY.register(CONF_BINARY_SENSOR_TEMPLATE_PUBLISH,
BINARY_SENSOR_TEMPLATE_PUBLISH_ACTION_SCHEMA)
def binary_sensor_template_publish_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_binary_sensor_publish_action(template_arg)
type = BinarySensorPublishAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
for template_ in templatable(config[CONF_STATE], args, bool_):
yield None
add(action.set_state(template_))
yield action
def to_hass_config(data, config):
return binary_sensor.core_to_hass_config(data, config)

View File

@@ -0,0 +1,105 @@
import voluptuous as vol
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
from esphome.components import mqtt
from esphome.components.mqtt import setup_mqtt_component
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID
from esphome.core import CORE
from esphome.cpp_generator import Pvariable, add, get_variable
from esphome.cpp_types import Action, Nameable, esphome_ns
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
})
cover_ns = esphome_ns.namespace('cover')
Cover = cover_ns.class_('Cover', Nameable)
MQTTCoverComponent = cover_ns.class_('MQTTCoverComponent', mqtt.MQTTComponent)
CoverState = cover_ns.class_('CoverState')
COVER_OPEN = cover_ns.COVER_OPEN
COVER_CLOSED = cover_ns.COVER_CLOSED
validate_cover_state = cv.one_of('OPEN', 'CLOSED', upper=True)
COVER_STATES = {
'OPEN': COVER_OPEN,
'CLOSED': COVER_CLOSED,
}
# Actions
OpenAction = cover_ns.class_('OpenAction', Action)
CloseAction = cover_ns.class_('CloseAction', Action)
StopAction = cover_ns.class_('StopAction', Action)
COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(Cover),
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTCoverComponent),
})
COVER_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(COVER_SCHEMA.schema)
def setup_cover_core_(cover_var, config):
if CONF_INTERNAL in config:
add(cover_var.set_internal(config[CONF_INTERNAL]))
setup_mqtt_component(cover_var.Pget_mqtt(), config)
def setup_cover(cover_obj, config):
CORE.add_job(setup_cover_core_, cover_obj, config)
BUILD_FLAGS = '-DUSE_COVER'
CONF_COVER_OPEN = 'cover.open'
COVER_OPEN_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(Cover),
})
@ACTION_REGISTRY.register(CONF_COVER_OPEN, COVER_OPEN_ACTION_SCHEMA)
def cover_open_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_open_action(template_arg)
type = OpenAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
CONF_COVER_CLOSE = 'cover.close'
COVER_CLOSE_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(Cover),
})
@ACTION_REGISTRY.register(CONF_COVER_CLOSE, COVER_CLOSE_ACTION_SCHEMA)
def cover_close_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_close_action(template_arg)
type = CloseAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
CONF_COVER_STOP = 'cover.stop'
COVER_STOP_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(Cover),
})
@ACTION_REGISTRY.register(CONF_COVER_STOP, COVER_STOP_ACTION_SCHEMA)
def cover_stop_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_stop_action(template_arg)
type = StopAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
def core_to_hass_config(data, config):
ret = mqtt.build_hass_config(data, 'cover', config, include_state=True, include_command=True)
if ret is None:
return None
return ret

View File

@@ -0,0 +1,88 @@
import voluptuous as vol
from esphome import automation
from esphome.automation import ACTION_REGISTRY
from esphome.components import cover
import esphome.config_validation as cv
from esphome.const import CONF_ASSUMED_STATE, CONF_CLOSE_ACTION, CONF_ID, CONF_LAMBDA, CONF_NAME, \
CONF_OPEN_ACTION, CONF_OPTIMISTIC, CONF_STATE, CONF_STOP_ACTION
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda, templatable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import Action, App, optional
from esphome.py_compat import string_types
TemplateCover = cover.cover_ns.class_('TemplateCover', cover.Cover)
CoverPublishAction = cover.cover_ns.class_('CoverPublishAction', Action)
PLATFORM_SCHEMA = cv.nameable(cover.COVER_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(TemplateCover),
vol.Optional(CONF_LAMBDA): cv.lambda_,
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_ASSUMED_STATE): cv.boolean,
vol.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True),
vol.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
vol.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
rhs = App.make_template_cover(config[CONF_NAME])
var = Pvariable(config[CONF_ID], rhs)
cover.setup_cover(var, config)
setup_component(var, config)
if CONF_LAMBDA in config:
for template_ in process_lambda(config[CONF_LAMBDA], [],
return_type=optional.template(cover.CoverState)):
yield
add(var.set_state_lambda(template_))
if CONF_OPEN_ACTION in config:
automation.build_automations(var.get_open_trigger(), [],
config[CONF_OPEN_ACTION])
if CONF_CLOSE_ACTION in config:
automation.build_automations(var.get_close_trigger(), [],
config[CONF_CLOSE_ACTION])
if CONF_STOP_ACTION in config:
automation.build_automations(var.get_stop_trigger(), [],
config[CONF_STOP_ACTION])
if CONF_OPTIMISTIC in config:
add(var.set_optimistic(config[CONF_OPTIMISTIC]))
if CONF_ASSUMED_STATE in config:
add(var.set_assumed_state(config[CONF_ASSUMED_STATE]))
BUILD_FLAGS = '-DUSE_TEMPLATE_COVER'
CONF_COVER_TEMPLATE_PUBLISH = 'cover.template.publish'
COVER_TEMPLATE_PUBLISH_ACTION_SCHEMA = cv.Schema({
vol.Required(CONF_ID): cv.use_variable_id(cover.Cover),
vol.Required(CONF_STATE): cv.templatable(cover.validate_cover_state),
})
@ACTION_REGISTRY.register(CONF_COVER_TEMPLATE_PUBLISH,
COVER_TEMPLATE_PUBLISH_ACTION_SCHEMA)
def cover_template_publish_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_cover_publish_action(template_arg)
type = CoverPublishAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
state = config[CONF_STATE]
if isinstance(state, string_types):
template_ = cover.COVER_STATES[state]
else:
for template_ in templatable(state, args, cover.CoverState):
yield None
add(action.set_state(template_))
yield action
def to_hass_config(data, config):
ret = cover.core_to_hass_config(data, config)
if ret is None:
return None
if CONF_OPTIMISTIC in config:
ret['optimistic'] = config[CONF_OPTIMISTIC]
return ret

View File

@@ -0,0 +1,33 @@
import voluptuous as vol
import esphome.config_validation as cv
from esphome.const import CONF_COMPONENTS, CONF_ID, CONF_LAMBDA
from esphome.cpp_generator import Pvariable, process_lambda, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import Component, ComponentPtr, esphome_ns, std_vector
CustomComponentConstructor = esphome_ns.class_('CustomComponentConstructor')
MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(CustomComponentConstructor),
vol.Required(CONF_LAMBDA): cv.lambda_,
vol.Optional(CONF_COMPONENTS): cv.ensure_list(cv.Schema({
cv.GenerateID(): cv.declare_variable_id(Component)
}).extend(cv.COMPONENT_SCHEMA.schema)),
})
def to_code(config):
for template_ in process_lambda(config[CONF_LAMBDA], [],
return_type=std_vector.template(ComponentPtr)):
yield
rhs = CustomComponentConstructor(template_)
custom = variable(config[CONF_ID], rhs)
for i, comp_config in enumerate(config.get(CONF_COMPONENTS, [])):
comp = Pvariable(comp_config[CONF_ID], custom.get_component(i))
setup_component(comp, comp_config)
BUILD_FLAGS = '-DUSE_CUSTOM_COMPONENT'

View File

@@ -0,0 +1,27 @@
import voluptuous as vol
from esphome import pins
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_PIN, CONF_UPDATE_INTERVAL
from esphome.cpp_generator import Pvariable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, PollingComponent
DallasComponent = sensor.sensor_ns.class_('DallasComponent', PollingComponent)
MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(DallasComponent),
vol.Required(CONF_PIN): pins.input_pin,
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.make_dallas_component(config[CONF_PIN], config.get(CONF_UPDATE_INTERVAL))
var = Pvariable(config[CONF_ID], rhs)
setup_component(var, config)
BUILD_FLAGS = '-DUSE_DALLAS_SENSOR'

View File

@@ -0,0 +1,14 @@
import esphome.config_validation as cv
from esphome.cpp_generator import add
from esphome.cpp_types import App
DEPENDENCIES = ['logger']
CONFIG_SCHEMA = cv.Schema({})
def to_code(config):
add(App.make_debug_component())
BUILD_FLAGS = '-DUSE_DEBUG_COMPONENT'

View File

@@ -0,0 +1,118 @@
import voluptuous as vol
from esphome import config_validation as cv, pins
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
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.cpp_generator import Pvariable, StructInitializer, add, get_variable
from esphome.cpp_helpers import gpio_input_pin_expression, setup_component
from esphome.cpp_types import Action, App, Component, esphome_ns, global_ns
def validate_pin_number(value):
valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 39]
if value[CONF_NUMBER] not in valid_pins:
raise vol.Invalid(u"Only pins {} support wakeup"
u"".format(', '.join(str(x) for x in valid_pins)))
return value
DeepSleepComponent = esphome_ns.class_('DeepSleepComponent', Component)
EnterDeepSleepAction = esphome_ns.class_('EnterDeepSleepAction', Action)
PreventDeepSleepAction = esphome_ns.class_('PreventDeepSleepAction', Action)
WakeupPinMode = esphome_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,
}
esp_sleep_ext1_wakeup_mode_t = global_ns.enum('esp_sleep_ext1_wakeup_mode_t')
Ext1Wakeup = esphome_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,
}
CONF_WAKEUP_PIN_MODE = 'wakeup_pin_mode'
CONF_ESP32_EXT1_WAKEUP = 'esp32_ext1_wakeup'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(DeepSleepComponent),
vol.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds,
vol.Optional(CONF_WAKEUP_PIN): vol.All(cv.only_on_esp32, pins.internal_gpio_input_pin_schema,
validate_pin_number),
vol.Optional(CONF_WAKEUP_PIN_MODE): vol.All(cv.only_on_esp32,
cv.one_of(*WAKEUP_PIN_MODES), upper=True),
vol.Optional(CONF_ESP32_EXT1_WAKEUP): vol.All(cv.only_on_esp32, cv.Schema({
vol.Required(CONF_PINS): cv.ensure_list(pins.shorthand_input_pin, validate_pin_number),
vol.Required(CONF_MODE): cv.one_of(*EXT1_WAKEUP_MODES, upper=True),
})),
vol.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds,
vol.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.schema)
def to_code(config):
rhs = App.make_deep_sleep_component()
deep_sleep = Pvariable(config[CONF_ID], rhs)
if CONF_SLEEP_DURATION in config:
add(deep_sleep.set_sleep_duration(config[CONF_SLEEP_DURATION]))
if CONF_WAKEUP_PIN in config:
for pin in gpio_input_pin_expression(config[CONF_WAKEUP_PIN]):
yield
add(deep_sleep.set_wakeup_pin(pin))
if CONF_WAKEUP_PIN_MODE in config:
add(deep_sleep.set_wakeup_pin_mode(WAKEUP_PIN_MODES[config[CONF_WAKEUP_PIN_MODE]]))
if CONF_RUN_DURATION in config:
add(deep_sleep.set_run_duration(config[CONF_RUN_DURATION]))
if CONF_ESP32_EXT1_WAKEUP in config:
conf = config[CONF_ESP32_EXT1_WAKEUP]
mask = 0
for pin in conf[CONF_PINS]:
mask |= 1 << pin[CONF_NUMBER]
struct = StructInitializer(
Ext1Wakeup,
('mask', mask),
('wakeup_mode', EXT1_WAKEUP_MODES[conf[CONF_MODE]])
)
add(deep_sleep.set_ext1_wakeup(struct))
setup_component(deep_sleep, config)
BUILD_FLAGS = '-DUSE_DEEP_SLEEP'
CONF_DEEP_SLEEP_ENTER = 'deep_sleep.enter'
DEEP_SLEEP_ENTER_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(DeepSleepComponent),
})
@ACTION_REGISTRY.register(CONF_DEEP_SLEEP_ENTER, DEEP_SLEEP_ENTER_ACTION_SCHEMA)
def deep_sleep_enter_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_enter_deep_sleep_action(template_arg)
type = EnterDeepSleepAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
CONF_DEEP_SLEEP_PREVENT = 'deep_sleep.prevent'
DEEP_SLEEP_PREVENT_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(DeepSleepComponent),
})
@ACTION_REGISTRY.register(CONF_DEEP_SLEEP_PREVENT, DEEP_SLEEP_PREVENT_ACTION_SCHEMA)
def deep_sleep_prevent_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_prevent_deep_sleep_action(template_arg)
type = PreventDeepSleepAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)

View File

@@ -0,0 +1,127 @@
# coding=utf-8
import voluptuous as vol
from esphome import core
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
import esphome.config_validation as cv
from esphome.const import CONF_LAMBDA, CONF_ROTATION, CONF_UPDATE_INTERVAL, CONF_PAGES, CONF_ID
from esphome.core import CORE
from esphome.cpp_generator import add, process_lambda, Pvariable, templatable, get_variable
from esphome.cpp_types import esphome_ns, void, Action
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
})
display_ns = 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', Action)
DisplayPageShowNextAction = display_ns.class_('DisplayPageShowNextAction', Action)
DisplayPageShowPrevAction = display_ns.class_('DisplayPageShowPrevAction', Action)
DISPLAY_ROTATIONS = {
0: display_ns.DISPLAY_ROTATION_0_DEGREES,
90: display_ns.DISPLAY_ROTATION_90_DEGREES,
180: display_ns.DISPLAY_ROTATION_180_DEGREES,
270: display_ns.DISPLAY_ROTATION_270_DEGREES,
}
def validate_rotation(value):
value = cv.string(value)
if value.endswith(u"°"):
value = value[:-1]
try:
value = int(value)
except ValueError:
raise vol.Invalid(u"Expected integer for rotation")
return cv.one_of(*DISPLAY_ROTATIONS)(value)
BASIC_DISPLAY_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
vol.Optional(CONF_LAMBDA): cv.lambda_,
})
FULL_DISPLAY_PLATFORM_SCHEMA = BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ROTATION): validate_rotation,
vol.Optional(CONF_PAGES): vol.All(cv.ensure_list({
cv.GenerateID(): cv.declare_variable_id(DisplayPage),
vol.Required(CONF_LAMBDA): cv.lambda_,
}), vol.Length(min=1)),
})
def setup_display_core_(display_var, config):
if CONF_UPDATE_INTERVAL in config:
add(display_var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
if CONF_ROTATION in config:
add(display_var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]]))
if CONF_PAGES in config:
pages = []
for conf in config[CONF_PAGES]:
for lambda_ in process_lambda(conf[CONF_LAMBDA], [(DisplayBufferRef, 'it')],
return_type=void):
yield
var = Pvariable(conf[CONF_ID], DisplayPage.new(lambda_))
pages.append(var)
add(display_var.set_pages(pages))
CONF_DISPLAY_PAGE_SHOW = 'display.page.show'
DISPLAY_PAGE_SHOW_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.templatable(cv.use_variable_id(DisplayPage)),
})
@ACTION_REGISTRY.register(CONF_DISPLAY_PAGE_SHOW, DISPLAY_PAGE_SHOW_ACTION_SCHEMA)
def display_page_show_to_code(config, action_id, template_arg, args):
type = DisplayPageShowAction.template(template_arg)
action = Pvariable(action_id, type.new(), type=type)
if isinstance(config[CONF_ID], core.Lambda):
for template_ in templatable(config[CONF_ID], args, DisplayPagePtr):
yield None
add(action.set_page(template_))
else:
for var in get_variable(config[CONF_ID]):
yield None
add(action.set_page(var))
yield action
CONF_DISPLAY_PAGE_SHOW_NEXT = 'display.page.show_next'
DISPLAY_PAGE_SHOW_NEXT_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.templatable(cv.use_variable_id(DisplayBuffer)),
})
@ACTION_REGISTRY.register(CONF_DISPLAY_PAGE_SHOW_NEXT, DISPLAY_PAGE_SHOW_NEXT_ACTION_SCHEMA)
def display_page_show_next_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
type = DisplayPageShowNextAction.template(template_arg)
yield Pvariable(action_id, type.new(var), type=type)
CONF_DISPLAY_PAGE_SHOW_PREVIOUS = 'display.page.show_previous'
DISPLAY_PAGE_SHOW_PREVIOUS_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.templatable(cv.use_variable_id(DisplayBuffer)),
})
@ACTION_REGISTRY.register(CONF_DISPLAY_PAGE_SHOW_PREVIOUS, DISPLAY_PAGE_SHOW_PREVIOUS_ACTION_SCHEMA)
def display_page_show_previous_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
type = DisplayPageShowPrevAction.template(template_arg)
yield Pvariable(action_id, type.new(var), type=type)
def setup_display(display_var, config):
CORE.add_job(setup_display_core_, display_var, config)
BUILD_FLAGS = '-DUSE_DISPLAY'

View File

@@ -0,0 +1,76 @@
import voluptuous as vol
from esphome import pins
from esphome.components import display
import esphome.config_validation as cv
from esphome.const import CONF_DATA_PINS, CONF_DIMENSIONS, CONF_ENABLE_PIN, CONF_ID, \
CONF_LAMBDA, CONF_RS_PIN, CONF_RW_PIN
from esphome.cpp_generator import Pvariable, add, process_lambda
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
from esphome.cpp_types import App, PollingComponent, void
LCDDisplay = display.display_ns.class_('LCDDisplay', PollingComponent)
LCDDisplayRef = LCDDisplay.operator('ref')
GPIOLCDDisplay = display.display_ns.class_('GPIOLCDDisplay', LCDDisplay)
def validate_lcd_dimensions(value):
value = cv.dimensions(value)
if value[0] > 0x40:
raise vol.Invalid("LCD displays can't have more than 64 columns")
if value[1] > 4:
raise vol.Invalid("LCD displays can't have more than 4 rows")
return value
def validate_pin_length(value):
if len(value) != 4 and len(value) != 8:
raise vol.Invalid("LCD Displays can either operate in 4-pin or 8-pin mode,"
"not {}-pin mode".format(len(value)))
return value
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(GPIOLCDDisplay),
vol.Required(CONF_DIMENSIONS): validate_lcd_dimensions,
vol.Required(CONF_DATA_PINS): vol.All([pins.gpio_output_pin_schema], validate_pin_length),
vol.Required(CONF_ENABLE_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_RS_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_RW_PIN): pins.gpio_output_pin_schema,
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.make_gpio_lcd_display(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])
lcd = Pvariable(config[CONF_ID], rhs)
pins_ = []
for conf in config[CONF_DATA_PINS]:
for pin in gpio_output_pin_expression(conf):
yield
pins_.append(pin)
add(lcd.set_data_pins(*pins_))
for enable in gpio_output_pin_expression(config[CONF_ENABLE_PIN]):
yield
add(lcd.set_enable_pin(enable))
for rs in gpio_output_pin_expression(config[CONF_RS_PIN]):
yield
add(lcd.set_rs_pin(rs))
if CONF_RW_PIN in config:
for rw in gpio_output_pin_expression(config[CONF_RW_PIN]):
yield
add(lcd.set_rw_pin(rw))
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA], [(LCDDisplayRef, 'it')],
return_type=void):
yield
add(lcd.set_writer(lambda_))
display.setup_display(lcd, config)
setup_component(lcd, config)
BUILD_FLAGS = '-DUSE_LCD_DISPLAY'

View File

@@ -0,0 +1,40 @@
import voluptuous as vol
from esphome.components import display, i2c
from esphome.components.display.lcd_gpio import LCDDisplay, LCDDisplayRef, \
validate_lcd_dimensions
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_DIMENSIONS, CONF_ID, CONF_LAMBDA
from esphome.cpp_generator import Pvariable, add, process_lambda
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, void
DEPENDENCIES = ['i2c']
PCF8574LCDDisplay = display.display_ns.class_('PCF8574LCDDisplay', LCDDisplay, i2c.I2CDevice)
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(PCF8574LCDDisplay),
vol.Required(CONF_DIMENSIONS): validate_lcd_dimensions,
vol.Optional(CONF_ADDRESS): cv.i2c_address,
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.make_pcf8574_lcd_display(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])
lcd = Pvariable(config[CONF_ID], rhs)
if CONF_ADDRESS in config:
add(lcd.set_address(config[CONF_ADDRESS]))
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA], [(LCDDisplayRef, 'it')],
return_type=void):
yield
add(lcd.set_writer(lambda_))
display.setup_display(lcd, config)
setup_component(lcd, config)
BUILD_FLAGS = ['-DUSE_LCD_DISPLAY', '-DUSE_LCD_DISPLAY_PCF8574']

View File

@@ -0,0 +1,51 @@
import voluptuous as vol
from esphome import pins
from esphome.components import display, spi
from esphome.components.spi import SPIComponent
import esphome.config_validation as cv
from esphome.const import CONF_CS_PIN, CONF_ID, CONF_INTENSITY, CONF_LAMBDA, CONF_NUM_CHIPS, \
CONF_SPI_ID
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
from esphome.cpp_types import App, PollingComponent, void
DEPENDENCIES = ['spi']
MAX7219Component = display.display_ns.class_('MAX7219Component', PollingComponent, spi.SPIDevice)
MAX7219ComponentRef = MAX7219Component.operator('ref')
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(MAX7219Component),
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_NUM_CHIPS): vol.All(cv.uint8_t, vol.Range(min=1)),
vol.Optional(CONF_INTENSITY): vol.All(cv.uint8_t, vol.Range(min=0, max=15)),
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
for spi_ in get_variable(config[CONF_SPI_ID]):
yield
for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
yield
rhs = App.make_max7219(spi_, cs)
max7219 = Pvariable(config[CONF_ID], rhs)
if CONF_NUM_CHIPS in config:
add(max7219.set_num_chips(config[CONF_NUM_CHIPS]))
if CONF_INTENSITY in config:
add(max7219.set_intensity(config[CONF_INTENSITY]))
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA], [(MAX7219ComponentRef, 'it')],
return_type=void):
yield
add(max7219.set_writer(lambda_))
display.setup_display(max7219, config)
setup_component(max7219, config)
BUILD_FLAGS = '-DUSE_MAX7219'

View File

@@ -0,0 +1,36 @@
from esphome.components import display, uart
from esphome.components.uart import UARTComponent
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_UART_ID
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, PollingComponent, void
DEPENDENCIES = ['uart']
Nextion = display.display_ns.class_('Nextion', PollingComponent, uart.UARTDevice)
NextionRef = Nextion.operator('ref')
PLATFORM_SCHEMA = display.BASIC_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(Nextion),
cv.GenerateID(CONF_UART_ID): cv.use_variable_id(UARTComponent),
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
for uart_ in get_variable(config[CONF_UART_ID]):
yield
rhs = App.make_nextion(uart_)
nextion = Pvariable(config[CONF_ID], rhs)
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA], [(NextionRef, 'it')],
return_type=void):
yield
add(nextion.set_writer(lambda_))
display.setup_display(nextion, config)
setup_component(nextion, config)
BUILD_FLAGS = '-DUSE_NEXTION'

View File

@@ -0,0 +1,48 @@
import voluptuous as vol
from esphome import pins
from esphome.components import display
from esphome.components.display import ssd1306_spi
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_EXTERNAL_VCC, CONF_ID, CONF_LAMBDA, CONF_MODEL, \
CONF_PAGES, CONF_RESET_PIN
from esphome.cpp_generator import Pvariable, add, process_lambda
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
from esphome.cpp_types import App, void
DEPENDENCIES = ['i2c']
I2CSSD1306 = display.display_ns.class_('I2CSSD1306', ssd1306_spi.SSD1306)
PLATFORM_SCHEMA = vol.All(display.FULL_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(I2CSSD1306),
vol.Required(CONF_MODEL): ssd1306_spi.SSD1306_MODEL,
vol.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_EXTERNAL_VCC): cv.boolean,
vol.Optional(CONF_ADDRESS): cv.i2c_address,
}).extend(cv.COMPONENT_SCHEMA.schema), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
def to_code(config):
ssd = Pvariable(config[CONF_ID], App.make_i2c_ssd1306())
add(ssd.set_model(ssd1306_spi.MODELS[config[CONF_MODEL]]))
if CONF_RESET_PIN in config:
for reset in gpio_output_pin_expression(config[CONF_RESET_PIN]):
yield
add(ssd.set_reset_pin(reset))
if CONF_EXTERNAL_VCC in config:
add(ssd.set_external_vcc(config[CONF_EXTERNAL_VCC]))
if CONF_ADDRESS in config:
add(ssd.set_address(config[CONF_ADDRESS]))
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA],
[(display.DisplayBufferRef, 'it')], return_type=void):
yield
add(ssd.set_writer(lambda_))
display.setup_display(ssd, config)
setup_component(ssd, config)
BUILD_FLAGS = '-DUSE_SSD1306'

View File

@@ -0,0 +1,71 @@
import voluptuous as vol
from esphome import pins
from esphome.components import display, spi
from esphome.components.spi import SPIComponent
import esphome.config_validation as cv
from esphome.const import CONF_CS_PIN, CONF_DC_PIN, CONF_EXTERNAL_VCC, CONF_ID, CONF_LAMBDA, \
CONF_MODEL, CONF_RESET_PIN, CONF_SPI_ID, CONF_PAGES
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
from esphome.cpp_types import App, PollingComponent, void
DEPENDENCIES = ['spi']
SSD1306 = display.display_ns.class_('SSD1306', PollingComponent, display.DisplayBuffer)
SPISSD1306 = display.display_ns.class_('SPISSD1306', SSD1306, spi.SPIDevice)
SSD1306Model = display.display_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_MODEL = cv.one_of(*MODELS, upper=True, space="_")
PLATFORM_SCHEMA = vol.All(display.FULL_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(SPISSD1306),
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_MODEL): SSD1306_MODEL,
vol.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_EXTERNAL_VCC): cv.boolean,
}).extend(cv.COMPONENT_SCHEMA.schema), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
def to_code(config):
for spi_ in get_variable(config[CONF_SPI_ID]):
yield
for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
yield
for dc in gpio_output_pin_expression(config[CONF_DC_PIN]):
yield
rhs = App.make_spi_ssd1306(spi_, cs, dc)
ssd = Pvariable(config[CONF_ID], rhs)
add(ssd.set_model(MODELS[config[CONF_MODEL]]))
if CONF_RESET_PIN in config:
for reset in gpio_output_pin_expression(config[CONF_RESET_PIN]):
yield
add(ssd.set_reset_pin(reset))
if CONF_EXTERNAL_VCC in config:
add(ssd.set_external_vcc(config[CONF_EXTERNAL_VCC]))
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA],
[(display.DisplayBufferRef, 'it')], return_type=void):
yield
add(ssd.set_writer(lambda_))
display.setup_display(ssd, config)
setup_component(ssd, config)
BUILD_FLAGS = '-DUSE_SSD1306'

View File

@@ -0,0 +1,93 @@
import voluptuous as vol
from esphome import pins
from esphome.components import display, spi
from esphome.components.spi import SPIComponent
import esphome.config_validation as cv
from esphome.const import CONF_BUSY_PIN, CONF_CS_PIN, CONF_DC_PIN, CONF_FULL_UPDATE_EVERY, \
CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_PAGES, CONF_RESET_PIN, CONF_SPI_ID
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
from esphome.cpp_helpers import gpio_input_pin_expression, gpio_output_pin_expression, \
setup_component
from esphome.cpp_types import App, PollingComponent, void
DEPENDENCIES = ['spi']
WaveshareEPaperTypeA = display.display_ns.WaveshareEPaperTypeA
WaveshareEPaper = display.display_ns.class_('WaveshareEPaper',
PollingComponent, spi.SPIDevice, display.DisplayBuffer)
WaveshareEPaperTypeAModel = display.display_ns.enum('WaveshareEPaperTypeAModel')
WaveshareEPaperTypeBModel = display.display_ns.enum('WaveshareEPaperTypeBModel')
MODELS = {
'1.54in': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN),
'2.13in': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN),
'2.90in': ('a', WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN),
'2.70in': ('b', WaveshareEPaperTypeBModel.WAVESHARE_EPAPER_2_7_IN),
'4.20in': ('b', WaveshareEPaperTypeBModel.WAVESHARE_EPAPER_4_2_IN),
'7.50in': ('b', WaveshareEPaperTypeBModel.WAVESHARE_EPAPER_7_5_IN),
}
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 vol.Invalid("The 'full_update_every' option is only available for models "
"'1.54in', '2.13in' and '2.90in'.")
return value
PLATFORM_SCHEMA = vol.All(display.FULL_DISPLAY_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(WaveshareEPaper),
cv.GenerateID(CONF_SPI_ID): cv.use_variable_id(SPIComponent),
vol.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True),
vol.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
vol.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t,
}).extend(cv.COMPONENT_SCHEMA.schema), validate_full_update_every_only_type_a,
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA))
def to_code(config):
for spi_ in get_variable(config[CONF_SPI_ID]):
yield
for cs in gpio_output_pin_expression(config[CONF_CS_PIN]):
yield
for dc in gpio_output_pin_expression(config[CONF_DC_PIN]):
yield
model_type, model = MODELS[config[CONF_MODEL]]
if model_type == 'a':
rhs = App.make_waveshare_epaper_type_a(spi_, cs, dc, model)
epaper = Pvariable(config[CONF_ID], rhs, type=WaveshareEPaperTypeA)
elif model_type == 'b':
rhs = App.make_waveshare_epaper_type_b(spi_, cs, dc, model)
epaper = Pvariable(config[CONF_ID], rhs, type=WaveshareEPaper)
else:
raise NotImplementedError()
if CONF_LAMBDA in config:
for lambda_ in process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')],
return_type=void):
yield
add(epaper.set_writer(lambda_))
if CONF_RESET_PIN in config:
for reset in gpio_output_pin_expression(config[CONF_RESET_PIN]):
yield
add(epaper.set_reset_pin(reset))
if CONF_BUSY_PIN in config:
for reset in gpio_input_pin_expression(config[CONF_BUSY_PIN]):
yield
add(epaper.set_busy_pin(reset))
if CONF_FULL_UPDATE_EVERY in config:
add(epaper.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
display.setup_display(epaper, config)
setup_component(epaper, config)
BUILD_FLAGS = '-DUSE_WAVESHARE_EPAPER'

View File

@@ -0,0 +1,39 @@
import voluptuous as vol
from esphome import config_validation as cv
from esphome.const import CONF_ID, CONF_SCAN_INTERVAL, CONF_TYPE, CONF_UUID, ESP_PLATFORM_ESP32
from esphome.cpp_generator import Pvariable, RawExpression, add
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Component, esphome_ns
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
ESP32BLEBeacon = esphome_ns.class_('ESP32BLEBeacon', Component)
CONF_MAJOR = 'major'
CONF_MINOR = 'minor'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(ESP32BLEBeacon),
vol.Required(CONF_TYPE): cv.one_of('IBEACON', upper=True),
vol.Required(CONF_UUID): cv.uuid,
vol.Optional(CONF_MAJOR): cv.uint16_t,
vol.Optional(CONF_MINOR): cv.uint16_t,
vol.Optional(CONF_SCAN_INTERVAL): cv.positive_time_period_milliseconds,
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
uuid = config[CONF_UUID].hex
uuid_arr = [RawExpression('0x{}'.format(uuid[i:i + 2])) for i in range(0, len(uuid), 2)]
rhs = App.make_esp32_ble_beacon(uuid_arr)
ble = Pvariable(config[CONF_ID], rhs)
if CONF_MAJOR in config:
add(ble.set_major(config[CONF_MAJOR]))
if CONF_MINOR in config:
add(ble.set_minor(config[CONF_MINOR]))
setup_component(ble, config)
BUILD_FLAGS = '-DUSE_ESP32_BLE_BEACON'

View File

@@ -0,0 +1,40 @@
import voluptuous as vol
from esphome import config_validation as cv
from esphome.components import sensor
from esphome.const import CONF_ID, CONF_SCAN_INTERVAL, ESP_PLATFORM_ESP32
from esphome.core import HexInt
from esphome.cpp_generator import Pvariable, add
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Component, esphome_ns
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
CONF_ESP32_BLE_ID = 'esp32_ble_id'
ESP32BLETracker = esphome_ns.class_('ESP32BLETracker', Component)
XiaomiSensor = esphome_ns.class_('XiaomiSensor', sensor.Sensor)
XiaomiDevice = esphome_ns.class_('XiaomiDevice')
XIAOMI_SENSOR_SCHEMA = sensor.SENSOR_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(XiaomiSensor)
})
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(ESP32BLETracker),
vol.Optional(CONF_SCAN_INTERVAL): cv.positive_time_period_seconds,
}).extend(cv.COMPONENT_SCHEMA.schema)
def make_address_array(address):
return [HexInt(i) for i in address.parts]
def to_code(config):
rhs = App.make_esp32_ble_tracker()
ble = Pvariable(config[CONF_ID], rhs)
if CONF_SCAN_INTERVAL in config:
add(ble.set_scan_interval(config[CONF_SCAN_INTERVAL]))
setup_component(ble, config)
BUILD_FLAGS = '-DUSE_ESP32_BLE_TRACKER'

View File

@@ -0,0 +1,130 @@
import voluptuous as vol
from esphome import config_validation as cv, pins
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NAME, CONF_PIN, CONF_SCL, CONF_SDA, \
ESP_PLATFORM_ESP32
from esphome.cpp_generator import Pvariable, add
from esphome.cpp_types import App, Nameable, PollingComponent, esphome_ns
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
ESP32Camera = esphome_ns.class_('ESP32Camera', PollingComponent, Nameable)
ESP32CameraFrameSize = esphome_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,
}
CONF_DATA_PINS = 'data_pins'
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_RESET_PIN = 'reset_pin'
CONF_POWER_DOWN_PIN = 'power_down_pin'
CONF_MAX_FRAMERATE = 'max_framerate'
CONF_IDLE_FRAMERATE = 'idle_framerate'
CONF_RESOLUTION = 'resolution'
CONF_JPEG_QUALITY = 'jpeg_quality'
CONF_VERTICAL_FLIP = 'vertical_flip'
CONF_HORIZONTAL_MIRROR = 'horizontal_mirror'
CONF_CONTRAST = 'contrast'
CONF_BRIGHTNESS = 'brightness'
CONF_SATURATION = 'saturation'
CONF_TEST_PATTERN = 'test_pattern'
camera_range_param = vol.All(cv.int_, vol.Range(min=-2, max=2))
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(ESP32Camera),
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_DATA_PINS): vol.All([pins.input_pin], vol.Length(min=8, max=8)),
vol.Required(CONF_VSYNC_PIN): pins.input_pin,
vol.Required(CONF_HREF_PIN): pins.input_pin,
vol.Required(CONF_PIXEL_CLOCK_PIN): pins.input_pin,
vol.Required(CONF_EXTERNAL_CLOCK): vol.Schema({
vol.Required(CONF_PIN): pins.output_pin,
vol.Optional(CONF_FREQUENCY, default='20MHz'): vol.All(cv.frequency, vol.In([20e6, 10e6])),
}),
vol.Required(CONF_I2C_PINS): vol.Schema({
vol.Required(CONF_SDA): pins.output_pin,
vol.Required(CONF_SCL): pins.output_pin,
}),
vol.Optional(CONF_RESET_PIN): pins.output_pin,
vol.Optional(CONF_POWER_DOWN_PIN): pins.output_pin,
vol.Optional(CONF_MAX_FRAMERATE, default='10 fps'): vol.All(cv.framerate,
vol.Range(min=0, min_included=False,
max=60)),
vol.Optional(CONF_IDLE_FRAMERATE, default='0.1 fps'): vol.All(cv.framerate,
vol.Range(min=0, max=1)),
vol.Optional(CONF_RESOLUTION, default='640X480'): cv.one_of(*FRAME_SIZES, upper=True),
vol.Optional(CONF_JPEG_QUALITY, default=10): vol.All(cv.int_, vol.Range(min=10, max=63)),
vol.Optional(CONF_CONTRAST, default=0): camera_range_param,
vol.Optional(CONF_BRIGHTNESS, default=0): camera_range_param,
vol.Optional(CONF_SATURATION, default=0): camera_range_param,
vol.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean,
vol.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean,
vol.Optional(CONF_TEST_PATTERN, default=False): cv.boolean,
}).extend(cv.COMPONENT_SCHEMA.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',
}
def to_code(config):
rhs = App.register_component(ESP32Camera.new(config[CONF_NAME]))
cam = Pvariable(config[CONF_ID], rhs)
for key, setter in SETTERS.items():
if key in config:
add(getattr(cam, setter)(config[key]))
extclk = config[CONF_EXTERNAL_CLOCK]
add(cam.set_external_clock(extclk[CONF_PIN], extclk[CONF_FREQUENCY]))
i2c_pins = config[CONF_I2C_PINS]
add(cam.set_i2c_pins(i2c_pins[CONF_SDA], i2c_pins[CONF_SCL]))
add(cam.set_max_update_interval(1000 / config[CONF_MAX_FRAMERATE]))
if config[CONF_IDLE_FRAMERATE] == 0:
add(cam.set_idle_update_interval(0))
else:
add(cam.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE]))
add(cam.set_frame_size(FRAME_SIZES[config[CONF_RESOLUTION]]))
BUILD_FLAGS = '-DUSE_ESP32_CAMERA'

View File

@@ -0,0 +1,89 @@
import voluptuous as vol
from esphome import config_validation as cv
from esphome.components import binary_sensor
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
from esphome.cpp_generator import Pvariable, add
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Component, global_ns
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
def validate_voltage(values):
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'
return cv.one_of(*values)(value)
return validator
LOW_VOLTAGE_REFERENCE = {
'0.5V': global_ns.TOUCH_LVOLT_0V5,
'0.6V': global_ns.TOUCH_LVOLT_0V6,
'0.7V': global_ns.TOUCH_LVOLT_0V7,
'0.8V': global_ns.TOUCH_LVOLT_0V8,
}
HIGH_VOLTAGE_REFERENCE = {
'2.4V': global_ns.TOUCH_HVOLT_2V4,
'2.5V': global_ns.TOUCH_HVOLT_2V5,
'2.6V': global_ns.TOUCH_HVOLT_2V6,
'2.7V': global_ns.TOUCH_HVOLT_2V7,
}
VOLTAGE_ATTENUATION = {
'1.5V': global_ns.TOUCH_HVOLT_ATTEN_1V5,
'1V': global_ns.TOUCH_HVOLT_ATTEN_1V,
'0.5V': global_ns.TOUCH_HVOLT_ATTEN_0V5,
'0V': global_ns.TOUCH_HVOLT_ATTEN_0V,
}
ESP32TouchComponent = binary_sensor.binary_sensor_ns.class_('ESP32TouchComponent', Component)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(ESP32TouchComponent),
vol.Optional(CONF_SETUP_MODE): cv.boolean,
vol.Optional(CONF_IIR_FILTER): cv.positive_time_period_milliseconds,
vol.Optional(CONF_SLEEP_DURATION):
vol.All(cv.positive_time_period, vol.Range(max=TimePeriod(microseconds=436906))),
vol.Optional(CONF_MEASUREMENT_DURATION):
vol.All(cv.positive_time_period, vol.Range(max=TimePeriod(microseconds=8192))),
vol.Optional(CONF_LOW_VOLTAGE_REFERENCE): validate_voltage(LOW_VOLTAGE_REFERENCE),
vol.Optional(CONF_HIGH_VOLTAGE_REFERENCE): validate_voltage(HIGH_VOLTAGE_REFERENCE),
vol.Optional(CONF_VOLTAGE_ATTENUATION): validate_voltage(VOLTAGE_ATTENUATION),
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.make_esp32_touch_component()
touch = Pvariable(config[CONF_ID], rhs)
if CONF_SETUP_MODE in config:
add(touch.set_setup_mode(config[CONF_SETUP_MODE]))
if CONF_IIR_FILTER in config:
add(touch.set_iir_filter(config[CONF_IIR_FILTER]))
if CONF_SLEEP_DURATION in config:
sleep_duration = int(config[CONF_SLEEP_DURATION].total_microseconds * 0.15)
add(touch.set_sleep_duration(sleep_duration))
if CONF_MEASUREMENT_DURATION in config:
measurement_duration = int(config[CONF_MEASUREMENT_DURATION].total_microseconds * 0.125)
add(touch.set_measurement_duration(measurement_duration))
if CONF_LOW_VOLTAGE_REFERENCE in config:
value = LOW_VOLTAGE_REFERENCE[config[CONF_LOW_VOLTAGE_REFERENCE]]
add(touch.set_low_voltage_reference(value))
if CONF_HIGH_VOLTAGE_REFERENCE in config:
value = HIGH_VOLTAGE_REFERENCE[config[CONF_HIGH_VOLTAGE_REFERENCE]]
add(touch.set_high_voltage_reference(value))
if CONF_VOLTAGE_ATTENUATION in config:
value = VOLTAGE_ATTENUATION[config[CONF_VOLTAGE_ATTENUATION]]
add(touch.set_voltage_attenuation(value))
setup_component(touch, config)
BUILD_FLAGS = '-DUSE_ESP32_TOUCH_BINARY_SENSOR'

View File

@@ -0,0 +1,85 @@
import voluptuous as vol
from esphome import pins
from esphome.components import wifi
import esphome.config_validation as cv
from esphome.const import CONF_DOMAIN, CONF_ID, CONF_MANUAL_IP, CONF_STATIC_IP, CONF_TYPE, \
CONF_USE_ADDRESS, ESP_PLATFORM_ESP32
from esphome.core import CORE
from esphome.cpp_generator import Pvariable, add
from esphome.cpp_helpers import gpio_output_pin_expression
from esphome.cpp_types import App, Component, esphome_ns, global_ns
CONFLICTS_WITH = ['wifi']
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
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 = esphome_ns.enum('EthernetType')
ETHERNET_TYPES = {
'LAN8720': EthernetType.ETHERNET_TYPE_LAN8720,
'TLK110': EthernetType.ETHERNET_TYPE_TLK110,
}
eth_clock_mode_t = 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,
}
EthernetComponent = esphome_ns.class_('EthernetComponent', Component)
def validate(config):
if CONF_USE_ADDRESS not in config:
if CONF_MANUAL_IP in config:
use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP])
else:
use_address = CORE.name + config[CONF_DOMAIN]
config[CONF_USE_ADDRESS] = use_address
return config
CONFIG_SCHEMA = vol.All(cv.Schema({
cv.GenerateID(): cv.declare_variable_id(EthernetComponent),
vol.Required(CONF_TYPE): cv.one_of(*ETHERNET_TYPES, upper=True),
vol.Required(CONF_MDC_PIN): pins.output_pin,
vol.Required(CONF_MDIO_PIN): pins.input_output_pin,
vol.Optional(CONF_CLK_MODE, default='GPIO0_IN'): cv.one_of(*CLK_MODES, upper=True, space='_'),
vol.Optional(CONF_PHY_ADDR, default=0): vol.All(cv.int_, vol.Range(min=0, max=31)),
vol.Optional(CONF_POWER_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_MANUAL_IP): wifi.STA_MANUAL_IP_SCHEMA,
vol.Optional(CONF_DOMAIN, default='.local'): cv.domain_name,
vol.Optional(CONF_USE_ADDRESS): cv.string_strict,
vol.Optional('hostname'): cv.invalid("The hostname option has been removed in 1.11.0"),
}), validate)
def to_code(config):
rhs = App.init_ethernet()
eth = Pvariable(config[CONF_ID], rhs)
add(eth.set_phy_addr(config[CONF_PHY_ADDR]))
add(eth.set_mdc_pin(config[CONF_MDC_PIN]))
add(eth.set_mdio_pin(config[CONF_MDIO_PIN]))
add(eth.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
add(eth.set_clk_mode(CLK_MODES[config[CONF_CLK_MODE]]))
add(eth.set_use_address(config[CONF_USE_ADDRESS]))
if CONF_POWER_PIN in config:
for pin in gpio_output_pin_expression(config[CONF_POWER_PIN]):
yield
add(eth.set_power_pin(pin))
if CONF_MANUAL_IP in config:
add(eth.set_manual_ip(wifi.manual_ip(config[CONF_MANUAL_IP])))
REQUIRED_BUILD_FLAGS = '-DUSE_ETHERNET'

View File

@@ -0,0 +1,144 @@
import voluptuous as vol
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
from esphome.components import mqtt
from esphome.components.mqtt import setup_mqtt_component
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID, CONF_NAME, CONF_OSCILLATING, \
CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_OUTPUT, CONF_OSCILLATION_STATE_TOPIC, \
CONF_SPEED, CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC
from esphome.core import CORE
from esphome.cpp_generator import Pvariable, add, get_variable, templatable
from esphome.cpp_types import Action, Application, Component, Nameable, bool_, esphome_ns
from esphome.py_compat import string_types
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
})
fan_ns = esphome_ns.namespace('fan')
FanState = fan_ns.class_('FanState', Nameable, Component)
MQTTFanComponent = fan_ns.class_('MQTTFanComponent', mqtt.MQTTComponent)
MakeFan = Application.struct('MakeFan')
# Actions
TurnOnAction = fan_ns.class_('TurnOnAction', Action)
TurnOffAction = fan_ns.class_('TurnOffAction', Action)
ToggleAction = fan_ns.class_('ToggleAction', Action)
FanSpeed = fan_ns.enum('FanSpeed')
FAN_SPEED_OFF = FanSpeed.FAN_SPEED_OFF
FAN_SPEED_LOW = FanSpeed.FAN_SPEED_LOW
FAN_SPEED_MEDIUM = FanSpeed.FAN_SPEED_MEDIUM
FAN_SPEED_HIGH = FanSpeed.FAN_SPEED_HIGH
FAN_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(FanState),
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTFanComponent),
vol.Optional(CONF_OSCILLATION_STATE_TOPIC): vol.All(cv.requires_component('mqtt'),
cv.publish_topic),
vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): vol.All(cv.requires_component('mqtt'),
cv.subscribe_topic),
})
FAN_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(FAN_SCHEMA.schema)
FAN_SPEEDS = {
'OFF': FAN_SPEED_OFF,
'LOW': FAN_SPEED_LOW,
'MEDIUM': FAN_SPEED_MEDIUM,
'HIGH': FAN_SPEED_HIGH,
}
def setup_fan_core_(fan_var, config):
if CONF_INTERNAL in config:
add(fan_var.set_internal(config[CONF_INTERNAL]))
mqtt_ = fan_var.Pget_mqtt()
if CONF_OSCILLATION_STATE_TOPIC in config:
add(mqtt_.set_custom_oscillation_state_topic(config[CONF_OSCILLATION_STATE_TOPIC]))
if CONF_OSCILLATION_COMMAND_TOPIC in config:
add(mqtt_.set_custom_oscillation_command_topic(config[CONF_OSCILLATION_COMMAND_TOPIC]))
if CONF_SPEED_STATE_TOPIC in config:
add(mqtt_.set_custom_speed_state_topic(config[CONF_SPEED_STATE_TOPIC]))
if CONF_SPEED_COMMAND_TOPIC in config:
add(mqtt_.set_custom_speed_command_topic(config[CONF_SPEED_COMMAND_TOPIC]))
setup_mqtt_component(mqtt_, config)
def setup_fan(fan_obj, config):
fan_var = Pvariable(config[CONF_ID], fan_obj, has_side_effects=False)
CORE.add_job(setup_fan_core_, fan_var, config)
BUILD_FLAGS = '-DUSE_FAN'
CONF_FAN_TOGGLE = 'fan.toggle'
FAN_TOGGLE_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(FanState),
})
@ACTION_REGISTRY.register(CONF_FAN_TOGGLE, FAN_TOGGLE_ACTION_SCHEMA)
def fan_toggle_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_toggle_action(template_arg)
type = ToggleAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
CONF_FAN_TURN_OFF = 'fan.turn_off'
FAN_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(FanState),
})
@ACTION_REGISTRY.register(CONF_FAN_TURN_OFF, FAN_TURN_OFF_ACTION_SCHEMA)
def fan_turn_off_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_turn_off_action(template_arg)
type = TurnOffAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
CONF_FAN_TURN_ON = 'fan.turn_on'
FAN_TURN_ON_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(FanState),
vol.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean),
vol.Optional(CONF_SPEED): cv.templatable(cv.one_of(*FAN_SPEEDS, upper=True)),
})
@ACTION_REGISTRY.register(CONF_FAN_TURN_ON, FAN_TURN_ON_ACTION_SCHEMA)
def fan_turn_on_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_turn_on_action(template_arg)
type = TurnOnAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
if CONF_OSCILLATING in config:
for template_ in templatable(config[CONF_OSCILLATING], args, bool_):
yield None
add(action.set_oscillating(template_))
if CONF_SPEED in config:
for template_ in templatable(config[CONF_SPEED], args, FanSpeed):
yield None
if isinstance(template_, string_types):
template_ = FAN_SPEEDS[template_]
add(action.set_speed(template_))
yield action
def core_to_hass_config(data, config):
ret = mqtt.build_hass_config(data, 'fan', config, include_state=True, include_command=True)
if ret is None:
return None
if CONF_OSCILLATION_OUTPUT in config:
default = mqtt.get_default_topic_for(data, 'fan', config[CONF_NAME], 'oscillation/state')
ret['oscillation_state_topic'] = config.get(CONF_OSCILLATION_STATE_TOPIC, default)
default = mqtt.get_default_topic_for(data, 'fan', config[CONF_NAME], 'oscillation/command')
ret['oscillation_command__topic'] = config.get(CONF_OSCILLATION_COMMAND_TOPIC, default)
return ret

View File

@@ -0,0 +1,34 @@
import voluptuous as vol
from esphome.components import fan, output
import esphome.config_validation as cv
from esphome.const import CONF_MAKE_ID, CONF_NAME, CONF_OSCILLATION_OUTPUT, CONF_OUTPUT
from esphome.cpp_generator import add, get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App
PLATFORM_SCHEMA = cv.nameable(fan.FAN_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(fan.MakeFan),
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.BinaryOutput),
vol.Optional(CONF_OSCILLATION_OUTPUT): cv.use_variable_id(output.BinaryOutput),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
for output_ in get_variable(config[CONF_OUTPUT]):
yield
rhs = App.make_fan(config[CONF_NAME])
fan_struct = variable(config[CONF_MAKE_ID], rhs)
add(fan_struct.Poutput.set_binary(output_))
if CONF_OSCILLATION_OUTPUT in config:
for oscillation_output in get_variable(config[CONF_OSCILLATION_OUTPUT]):
yield
add(fan_struct.Poutput.set_oscillation(oscillation_output))
fan.setup_fan(fan_struct.Pstate, config)
setup_component(fan_struct.Poutput, config)
def to_hass_config(data, config):
return fan.core_to_hass_config(data, config)

View File

@@ -0,0 +1,55 @@
import voluptuous as vol
from esphome.components import fan, mqtt, output
import esphome.config_validation as cv
from esphome.const import CONF_HIGH, CONF_LOW, CONF_MAKE_ID, CONF_MEDIUM, CONF_NAME, \
CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_SPEED, CONF_SPEED_COMMAND_TOPIC, \
CONF_SPEED_STATE_TOPIC
from esphome.cpp_generator import add, get_variable, variable
from esphome.cpp_types import App
PLATFORM_SCHEMA = cv.nameable(fan.FAN_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(fan.MakeFan),
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.FloatOutput),
vol.Optional(CONF_SPEED_STATE_TOPIC): cv.publish_topic,
vol.Optional(CONF_SPEED_COMMAND_TOPIC): cv.subscribe_topic,
vol.Optional(CONF_OSCILLATION_OUTPUT): cv.use_variable_id(output.BinaryOutput),
vol.Optional(CONF_SPEED): cv.Schema({
vol.Required(CONF_LOW): cv.percentage,
vol.Required(CONF_MEDIUM): cv.percentage,
vol.Required(CONF_HIGH): cv.percentage,
}),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
for output_ in get_variable(config[CONF_OUTPUT]):
yield
rhs = App.make_fan(config[CONF_NAME])
fan_struct = variable(config[CONF_MAKE_ID], rhs)
if CONF_SPEED in config:
speeds = config[CONF_SPEED]
add(fan_struct.Poutput.set_speed(output_,
speeds[CONF_LOW],
speeds[CONF_MEDIUM],
speeds[CONF_HIGH]))
else:
add(fan_struct.Poutput.set_speed(output_))
if CONF_OSCILLATION_OUTPUT in config:
for oscillation_output in get_variable(config[CONF_OSCILLATION_OUTPUT]):
yield
add(fan_struct.Poutput.set_oscillation(oscillation_output))
fan.setup_fan(fan_struct.Pstate, config)
def to_hass_config(data, config):
ret = fan.core_to_hass_config(data, config)
if ret is None:
return None
default = mqtt.get_default_topic_for(data, 'fan', config[CONF_NAME], 'speed/state')
ret['speed_state_topic'] = config.get(CONF_SPEED_STATE_TOPIC, default)
default = mqtt.get_default_topic_for(data, 'fan', config[CONF_NAME], 'speed/command')
ret['speed_command__topic'] = config.get(CONF_SPEED_COMMAND_TOPIC, default)
return ret

119
esphome/components/font.py Normal file
View File

@@ -0,0 +1,119 @@
# coding=utf-8
import voluptuous as vol
from esphome import core
from esphome.components import display
import esphome.config_validation as cv
from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE
from esphome.core import CORE, HexInt
from esphome.cpp_generator import Pvariable, progmem_array, safe_exp
from esphome.cpp_types import App, uint8
from esphome.py_compat import sort_by_cmp
DEPENDENCIES = ['display']
MULTI_CONF = True
Font = display.display_ns.class_('Font')
Glyph = display.display_ns.class_('Glyph')
def validate_glyphs(value):
if isinstance(value, list):
value = cv.Schema([cv.string])(value)
value = cv.Schema([cv.string])(list(value))
def comparator(x, y):
x_ = x.encode('utf-8')
y_ = y.encode('utf-8')
for c in range(min(len(x_), len(y_))):
if x_[c] < y_[c]:
return -1
if x_[c] > y_[c]:
return 1
if len(x_) < len(y_):
return -1
if len(x_) > len(y_):
return 1
raise vol.Invalid(u"Found duplicate glyph {}".format(x))
sort_by_cmp(value, comparator)
return value
def validate_pillow_installed(value):
try:
import PIL
except ImportError:
raise vol.Invalid("Please install the pillow python package to use this feature. "
"(pip install pillow)")
if PIL.__version__[0] < '4':
raise vol.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 vol.Invalid(u"Please unzip the font archive '{}' first and then use the .ttf files "
u"inside.".format(value))
if not value.endswith('.ttf'):
raise vol.Invalid(u"Only truetype (.ttf) files are supported. Please make sure you're "
u"using the correct format or rename the extension to .ttf")
return cv.file_(value)
DEFAULT_GLYPHS = u' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
CONF_RAW_DATA_ID = 'raw_data_id'
FONT_SCHEMA = cv.Schema({
vol.Required(CONF_ID): cv.declare_variable_id(Font),
vol.Required(CONF_FILE): validate_truetype_file,
vol.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
vol.Optional(CONF_SIZE, default=20): vol.All(cv.int_, vol.Range(min=1)),
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_variable_id(uint8),
})
CONFIG_SCHEMA = vol.All(validate_pillow_installed, FONT_SCHEMA)
def to_code(config):
from PIL import ImageFont
path = CORE.relative_path(config[CONF_FILE])
try:
font = ImageFont.truetype(path, config[CONF_SIZE])
except Exception as e:
raise core.EsphomeError(u"Could not load truetype file {}: {}".format(path, e))
ascent, descent = font.getmetrics()
glyph_args = {}
data = []
for glyph in config[CONF_GLYPHS]:
mask = font.getmask(glyph, mode='1')
_, (offset_x, offset_y) = font.font.getsize(glyph)
width, height = mask.size
width8 = ((width + 7) // 8) * 8
glyph_data = [0 for _ in range(height * width8 // 8)] # noqa: F812
for y in range(height):
for x in range(width):
if not mask.getpixel((x, y)):
continue
pos = x + y * width8
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
glyph_args[glyph] = (len(data), offset_x, offset_y, width, height)
data += glyph_data
rhs = safe_exp([HexInt(x) for x in data])
prog_arr = progmem_array(config[CONF_RAW_DATA_ID], rhs)
glyphs = []
for glyph in config[CONF_GLYPHS]:
glyphs.append(Glyph(glyph, prog_arr, *glyph_args[glyph]))
rhs = App.make_font(glyphs, ascent, ascent + descent)
Pvariable(config[CONF_ID], rhs)

View File

@@ -0,0 +1,35 @@
import voluptuous as vol
from esphome import config_validation as cv
from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE
from esphome.cpp_generator import Pvariable, RawExpression, TemplateArguments, add
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Component, esphome_ns
GlobalVariableComponent = esphome_ns.class_('GlobalVariableComponent', Component)
MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema({
vol.Required(CONF_ID): cv.declare_variable_id(GlobalVariableComponent),
vol.Required(CONF_TYPE): cv.string_strict,
vol.Optional(CONF_INITIAL_VALUE): cv.string_strict,
vol.Optional(CONF_RESTORE_VALUE): cv.boolean,
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
type_ = RawExpression(config[CONF_TYPE])
template_args = TemplateArguments(type_)
res_type = GlobalVariableComponent.template(template_args)
initial_value = None
if CONF_INITIAL_VALUE in config:
initial_value = RawExpression(config[CONF_INITIAL_VALUE])
rhs = App.Pmake_global_variable(template_args, initial_value)
glob = Pvariable(config[CONF_ID], rhs, type=res_type)
if config.get(CONF_RESTORE_VALUE, False):
hash_ = hash(config[CONF_ID].id) % 2**32
add(glob.set_restore_value(hash_))
setup_component(glob, config)

38
esphome/components/i2c.py Normal file
View File

@@ -0,0 +1,38 @@
import voluptuous as vol
from esphome import pins
import esphome.config_validation as cv
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_SCAN, CONF_SCL, \
CONF_SDA
from esphome.cpp_generator import Pvariable, add
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Component, esphome_ns
I2CComponent = esphome_ns.class_('I2CComponent', Component)
I2CDevice = pins.I2CDevice
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(I2CComponent),
vol.Optional(CONF_SDA, default='SDA'): pins.input_pin,
vol.Optional(CONF_SCL, default='SCL'): pins.input_pin,
vol.Optional(CONF_FREQUENCY): vol.All(cv.frequency, vol.Range(min=0, min_included=False)),
vol.Optional(CONF_SCAN, default=True): cv.boolean,
vol.Optional(CONF_RECEIVE_TIMEOUT): cv.invalid("The receive_timeout option has been removed "
"because timeouts are already handled by the "
"low-level i2c interface.")
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.init_i2c(config[CONF_SDA], config[CONF_SCL], config.get(CONF_SCAN))
i2c = Pvariable(config[CONF_ID], rhs)
if CONF_FREQUENCY in config:
add(i2c.set_frequency(config[CONF_FREQUENCY]))
setup_component(i2c, config)
BUILD_FLAGS = '-DUSE_I2C'
LIB_DEPS = 'Wire'

View File

@@ -0,0 +1,63 @@
# coding=utf-8
import logging
import voluptuous as vol
from esphome import core
from esphome.components import display, font
import esphome.config_validation as cv
from esphome.const import CONF_FILE, CONF_ID, CONF_RESIZE
from esphome.core import CORE, HexInt
from esphome.cpp_generator import Pvariable, progmem_array, safe_exp
from esphome.cpp_types import App, uint8
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['display']
MULTI_CONF = True
Image_ = display.display_ns.class_('Image')
CONF_RAW_DATA_ID = 'raw_data_id'
IMAGE_SCHEMA = cv.Schema({
vol.Required(CONF_ID): cv.declare_variable_id(Image_),
vol.Required(CONF_FILE): cv.file_,
vol.Optional(CONF_RESIZE): cv.dimensions,
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_variable_id(uint8),
})
CONFIG_SCHEMA = vol.All(font.validate_pillow_installed, IMAGE_SCHEMA)
def to_code(config):
from PIL import Image
path = CORE.relative_path(config[CONF_FILE])
try:
image = Image.open(path)
except Exception as e:
raise core.EsphomeError(u"Could not load image file {}: {}".format(path, e))
if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE])
image = image.convert('1', dither=Image.NONE)
width, height = image.size
if width > 500 or height > 500:
_LOGGER.warning("The image you requested is very big. Please consider using the resize "
"parameter")
width8 = ((width + 7) // 8) * 8
data = [0 for _ in range(height * width8 // 8)]
for y in range(height):
for x in range(width):
if image.getpixel((x, y)):
continue
pos = x + y * width8
data[pos // 8] |= 0x80 >> (pos % 8)
rhs = safe_exp([HexInt(x) for x in data])
prog_arr = progmem_array(config[CONF_RAW_DATA_ID], rhs)
rhs = App.make_image(prog_arr, width, height)
Pvariable(config[CONF_ID], rhs)

View File

@@ -0,0 +1,24 @@
import voluptuous as vol
from esphome import automation
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_INTERVAL
from esphome.cpp_generator import Pvariable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, PollingComponent, Trigger, esphome_ns
IntervalTrigger = esphome_ns.class_('IntervalTrigger', Trigger.template(), PollingComponent)
CONFIG_SCHEMA = automation.validate_automation(cv.Schema({
cv.GenerateID(): cv.declare_variable_id(IntervalTrigger),
vol.Required(CONF_INTERVAL): cv.positive_time_period_milliseconds,
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
for conf in config:
rhs = App.register_component(IntervalTrigger.new(conf[CONF_INTERVAL]))
trigger = Pvariable(conf[CONF_ID], rhs)
setup_component(trigger, conf)
automation.build_automations(trigger, [], conf)

View File

@@ -0,0 +1,522 @@
import voluptuous as vol
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
from esphome.components import mqtt
from esphome.components.mqtt import setup_mqtt_component
import esphome.config_validation as cv
from esphome.const import CONF_ALPHA, CONF_BLUE, CONF_BRIGHTNESS, CONF_COLORS, \
CONF_COLOR_TEMPERATURE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_DURATION, CONF_EFFECT, \
CONF_EFFECTS, CONF_EFFECT_ID, CONF_FLASH_LENGTH, CONF_GAMMA_CORRECT, CONF_GREEN, CONF_ID, \
CONF_INTERNAL, CONF_LAMBDA, CONF_MQTT_ID, CONF_NAME, CONF_NUM_LEDS, CONF_RANDOM, CONF_RED, \
CONF_SPEED, CONF_STATE, CONF_TRANSITION_LENGTH, CONF_UPDATE_INTERVAL, CONF_WHITE, CONF_WIDTH
from esphome.core import CORE
from esphome.cpp_generator import Pvariable, StructInitializer, add, get_variable, process_lambda, \
templatable
from esphome.cpp_types import Action, Application, Component, Nameable, esphome_ns, float_, \
std_string, uint32, void
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
})
# Base
light_ns = esphome_ns.namespace('light')
LightState = light_ns.class_('LightState', Nameable, Component)
# Fake class for addressable lights
AddressableLightState = light_ns.class_('LightState', LightState)
MakeLight = Application.struct('MakeLight')
LightOutput = light_ns.class_('LightOutput')
AddressableLight = light_ns.class_('AddressableLight')
AddressableLightRef = AddressableLight.operator('ref')
# Actions
ToggleAction = light_ns.class_('ToggleAction', Action)
TurnOffAction = light_ns.class_('TurnOffAction', Action)
TurnOnAction = light_ns.class_('TurnOnAction', Action)
LightColorValues = light_ns.class_('LightColorValues')
MQTTJSONLightComponent = light_ns.class_('MQTTJSONLightComponent', mqtt.MQTTComponent)
# Effects
LightEffect = light_ns.class_('LightEffect')
RandomLightEffect = light_ns.class_('RandomLightEffect', LightEffect)
LambdaLightEffect = light_ns.class_('LambdaLightEffect', 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)
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_ADD_LED_INTERVAL = 'add_led_interval'
CONF_REVERSE = 'reverse'
CONF_MOVE_INTERVAL = 'move_interval'
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_INTENSITY = 'intensity'
BINARY_EFFECTS = [CONF_LAMBDA, CONF_STROBE]
MONOCHROMATIC_EFFECTS = BINARY_EFFECTS + [CONF_FLICKER]
RGB_EFFECTS = MONOCHROMATIC_EFFECTS + [CONF_RANDOM]
ADDRESSABLE_EFFECTS = RGB_EFFECTS + [CONF_ADDRESSABLE_LAMBDA, CONF_ADDRESSABLE_RAINBOW,
CONF_ADDRESSABLE_COLOR_WIPE, CONF_ADDRESSABLE_SCAN,
CONF_ADDRESSABLE_TWINKLE, CONF_ADDRESSABLE_RANDOM_TWINKLE,
CONF_ADDRESSABLE_FIREWORKS, CONF_ADDRESSABLE_FLICKER]
EFFECTS_SCHEMA = cv.Schema({
vol.Optional(CONF_LAMBDA): cv.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_LAMBDA): cv.lambda_,
vol.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.positive_time_period_milliseconds,
}),
vol.Optional(CONF_RANDOM): cv.Schema({
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(RandomLightEffect),
vol.Optional(CONF_NAME, default="Random"): cv.string,
vol.Optional(CONF_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
}),
vol.Optional(CONF_STROBE): cv.Schema({
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(StrobeLightEffect),
vol.Optional(CONF_NAME, default="Strobe"): cv.string,
vol.Optional(CONF_COLORS): vol.All(cv.ensure_list(cv.Schema({
vol.Optional(CONF_STATE, default=True): cv.boolean,
vol.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
vol.Optional(CONF_RED, default=1.0): cv.percentage,
vol.Optional(CONF_GREEN, default=1.0): cv.percentage,
vol.Optional(CONF_BLUE, default=1.0): cv.percentage,
vol.Optional(CONF_WHITE, default=1.0): cv.percentage,
vol.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)), vol.Length(min=2)),
}),
vol.Optional(CONF_FLICKER): cv.Schema({
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(FlickerLightEffect),
vol.Optional(CONF_NAME, default="Flicker"): cv.string,
vol.Optional(CONF_ALPHA): cv.percentage,
vol.Optional(CONF_INTENSITY): cv.percentage,
}),
vol.Optional(CONF_ADDRESSABLE_LAMBDA): cv.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_LAMBDA): cv.lambda_,
vol.Optional(CONF_UPDATE_INTERVAL, default='0ms'): cv.positive_time_period_milliseconds,
}),
vol.Optional(CONF_ADDRESSABLE_RAINBOW): cv.Schema({
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableRainbowLightEffect),
vol.Optional(CONF_NAME, default="Rainbow"): cv.string,
vol.Optional(CONF_SPEED): cv.uint32_t,
vol.Optional(CONF_WIDTH): cv.uint32_t,
}),
vol.Optional(CONF_ADDRESSABLE_COLOR_WIPE): cv.Schema({
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableColorWipeEffect),
vol.Optional(CONF_NAME, default="Color Wipe"): cv.string,
vol.Optional(CONF_COLORS): cv.ensure_list({
vol.Optional(CONF_RED, default=1.0): cv.percentage,
vol.Optional(CONF_GREEN, default=1.0): cv.percentage,
vol.Optional(CONF_BLUE, default=1.0): cv.percentage,
vol.Optional(CONF_WHITE, default=1.0): cv.percentage,
vol.Optional(CONF_RANDOM, default=False): cv.boolean,
vol.Required(CONF_NUM_LEDS): vol.All(cv.uint32_t, vol.Range(min=1)),
}),
vol.Optional(CONF_ADD_LED_INTERVAL): cv.positive_time_period_milliseconds,
vol.Optional(CONF_REVERSE): cv.boolean,
}),
vol.Optional(CONF_ADDRESSABLE_SCAN): cv.Schema({
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableScanEffect),
vol.Optional(CONF_NAME, default="Scan"): cv.string,
vol.Optional(CONF_MOVE_INTERVAL): cv.positive_time_period_milliseconds,
}),
vol.Optional(CONF_ADDRESSABLE_TWINKLE): cv.Schema({
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableTwinkleEffect),
vol.Optional(CONF_NAME, default="Twinkle"): cv.string,
vol.Optional(CONF_TWINKLE_PROBABILITY): cv.percentage,
vol.Optional(CONF_PROGRESS_INTERVAL): cv.positive_time_period_milliseconds,
}),
vol.Optional(CONF_ADDRESSABLE_RANDOM_TWINKLE): cv.Schema({
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableRandomTwinkleEffect),
vol.Optional(CONF_NAME, default="Random Twinkle"): cv.string,
vol.Optional(CONF_TWINKLE_PROBABILITY): cv.percentage,
vol.Optional(CONF_PROGRESS_INTERVAL): cv.positive_time_period_milliseconds,
}),
vol.Optional(CONF_ADDRESSABLE_FIREWORKS): cv.Schema({
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableFireworksEffect),
vol.Optional(CONF_NAME, default="Fireworks"): cv.string,
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
vol.Optional(CONF_SPARK_PROBABILITY): cv.percentage,
vol.Optional(CONF_USE_RANDOM_COLOR): cv.boolean,
vol.Optional(CONF_FADE_OUT_RATE): cv.uint8_t,
}),
vol.Optional(CONF_ADDRESSABLE_FLICKER): cv.Schema({
cv.GenerateID(CONF_EFFECT_ID): cv.declare_variable_id(AddressableFlickerEffect),
vol.Optional(CONF_NAME, default="Addressable Flicker"): cv.string,
vol.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_milliseconds,
vol.Optional(CONF_INTENSITY): cv.percentage,
}),
})
def validate_effects(allowed_effects):
def validator(value):
is_list = isinstance(value, list)
if not is_list:
value = [value]
names = set()
ret = []
errors = []
for i, effect in enumerate(value):
path = [i] if is_list else []
if not isinstance(effect, dict):
errors.append(
vol.Invalid("Each effect must be a dictionary, not {}".format(type(value)),
path)
)
continue
if len(effect) > 1:
errors.append(
vol.Invalid("Each entry in the 'effects:' option must be a single effect.",
path)
)
continue
if not effect:
errors.append(
vol.Invalid("Found no effect for the {}th entry in 'effects:'!".format(i),
path)
)
continue
key = next(iter(effect.keys()))
if key.startswith('fastled'):
errors.append(
vol.Invalid("FastLED effects have been renamed to addressable effects. "
"Please use '{}'".format(key.replace('fastled', 'addressable')),
path)
)
continue
if key not in allowed_effects:
errors.append(
vol.Invalid("The effect '{}' does not exist or is not allowed for this "
"light type".format(key), path)
)
continue
effect[key] = effect[key] or {}
try:
conf = EFFECTS_SCHEMA(effect)
except vol.Invalid as err:
err.prepend(path)
errors.append(err)
continue
name = conf[key][CONF_NAME]
if name in names:
errors.append(
vol.Invalid(u"Found the effect name '{}' twice. All effects must have "
u"unique names".format(name), [i])
)
continue
names.add(name)
ret.append(conf)
if errors:
raise vol.MultipleInvalid(errors)
return ret
return validator
LIGHT_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(LightState),
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTJSONLightComponent),
})
LIGHT_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(LIGHT_SCHEMA.schema)
def build_effect(full_config):
key, config = next(iter(full_config.items()))
if key == CONF_LAMBDA:
lambda_ = None
for lambda_ in process_lambda(config[CONF_LAMBDA], [], return_type=void):
yield None
yield LambdaLightEffect.new(config[CONF_NAME], lambda_, config[CONF_UPDATE_INTERVAL])
elif key == CONF_RANDOM:
rhs = RandomLightEffect.new(config[CONF_NAME])
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
if CONF_TRANSITION_LENGTH in config:
add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH]))
if CONF_UPDATE_INTERVAL in config:
add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
yield effect
elif key == CONF_STROBE:
rhs = StrobeLightEffect.new(config[CONF_NAME])
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
colors = []
for color in config.get(CONF_COLORS, []):
colors.append(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]),
))
if colors:
add(effect.set_colors(colors))
yield effect
elif key == CONF_FLICKER:
rhs = FlickerLightEffect.new(config[CONF_NAME])
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
if CONF_ALPHA in config:
add(effect.set_alpha(config[CONF_ALPHA]))
if CONF_INTENSITY in config:
add(effect.set_intensity(config[CONF_INTENSITY]))
yield effect
elif key == CONF_ADDRESSABLE_LAMBDA:
args = [(AddressableLightRef, 'it')]
for lambda_ in process_lambda(config[CONF_LAMBDA], args, return_type=void):
yield None
yield AddressableLambdaLightEffect.new(config[CONF_NAME], lambda_,
config[CONF_UPDATE_INTERVAL])
elif key == CONF_ADDRESSABLE_RAINBOW:
rhs = AddressableRainbowLightEffect.new(config[CONF_NAME])
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
if CONF_SPEED in config:
add(effect.set_speed(config[CONF_SPEED]))
if CONF_WIDTH in config:
add(effect.set_width(config[CONF_WIDTH]))
yield effect
elif key == CONF_ADDRESSABLE_COLOR_WIPE:
rhs = AddressableColorWipeEffect.new(config[CONF_NAME])
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
if CONF_ADD_LED_INTERVAL in config:
add(effect.set_add_led_interval(config[CONF_ADD_LED_INTERVAL]))
if CONF_REVERSE in config:
add(effect.set_reverse(config[CONF_REVERSE]))
colors = []
for color in config.get(CONF_COLORS, []):
colors.append(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]),
))
if colors:
add(effect.set_colors(colors))
yield effect
elif key == CONF_ADDRESSABLE_SCAN:
rhs = AddressableScanEffect.new(config[CONF_NAME])
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
if CONF_MOVE_INTERVAL in config:
add(effect.set_move_interval(config[CONF_MOVE_INTERVAL]))
yield effect
elif key == CONF_ADDRESSABLE_TWINKLE:
rhs = AddressableTwinkleEffect.new(config[CONF_NAME])
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
if CONF_TWINKLE_PROBABILITY in config:
add(effect.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY]))
if CONF_PROGRESS_INTERVAL in config:
add(effect.set_progress_interval(config[CONF_PROGRESS_INTERVAL]))
yield effect
elif key == CONF_ADDRESSABLE_RANDOM_TWINKLE:
rhs = AddressableRandomTwinkleEffect.new(config[CONF_NAME])
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
if CONF_TWINKLE_PROBABILITY in config:
add(effect.set_twinkle_probability(config[CONF_TWINKLE_PROBABILITY]))
if CONF_PROGRESS_INTERVAL in config:
add(effect.set_progress_interval(config[CONF_PROGRESS_INTERVAL]))
yield effect
elif key == CONF_ADDRESSABLE_FIREWORKS:
rhs = AddressableFireworksEffect.new(config[CONF_NAME])
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
if CONF_UPDATE_INTERVAL in config:
add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
if CONF_SPARK_PROBABILITY in config:
add(effect.set_spark_probability(config[CONF_SPARK_PROBABILITY]))
if CONF_USE_RANDOM_COLOR in config:
add(effect.set_spark_probability(config[CONF_USE_RANDOM_COLOR]))
if CONF_FADE_OUT_RATE in config:
add(effect.set_spark_probability(config[CONF_FADE_OUT_RATE]))
yield effect
elif key == CONF_ADDRESSABLE_FLICKER:
rhs = AddressableFlickerEffect.new(config[CONF_NAME])
effect = Pvariable(config[CONF_EFFECT_ID], rhs)
if CONF_UPDATE_INTERVAL in config:
add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
if CONF_INTENSITY in config:
add(effect.set_intensity(config[CONF_INTENSITY]))
yield effect
else:
raise NotImplementedError("Effect {} not implemented".format(next(config.keys())))
def setup_light_core_(light_var, config):
if CONF_INTERNAL in config:
add(light_var.set_internal(config[CONF_INTERNAL]))
if CONF_DEFAULT_TRANSITION_LENGTH in config:
add(light_var.set_default_transition_length(config[CONF_DEFAULT_TRANSITION_LENGTH]))
if CONF_GAMMA_CORRECT in config:
add(light_var.set_gamma_correct(config[CONF_GAMMA_CORRECT]))
effects = []
for conf in config.get(CONF_EFFECTS, []):
for effect in build_effect(conf):
yield
effects.append(effect)
if effects:
add(light_var.add_effects(effects))
setup_mqtt_component(light_var.Pget_mqtt(), config)
def setup_light(light_obj, config):
light_var = Pvariable(config[CONF_ID], light_obj, has_side_effects=False)
CORE.add_job(setup_light_core_, light_var, config)
BUILD_FLAGS = '-DUSE_LIGHT'
CONF_LIGHT_TOGGLE = 'light.toggle'
LIGHT_TOGGLE_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(LightState),
vol.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
})
@ACTION_REGISTRY.register(CONF_LIGHT_TOGGLE, LIGHT_TOGGLE_ACTION_SCHEMA)
def light_toggle_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_toggle_action(template_arg)
type = ToggleAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
if CONF_TRANSITION_LENGTH in config:
for template_ in templatable(config[CONF_TRANSITION_LENGTH], args, uint32):
yield None
add(action.set_transition_length(template_))
yield action
CONF_LIGHT_TURN_OFF = 'light.turn_off'
LIGHT_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(LightState),
vol.Optional(CONF_TRANSITION_LENGTH): cv.templatable(cv.positive_time_period_milliseconds),
})
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_OFF, LIGHT_TURN_OFF_ACTION_SCHEMA)
def light_turn_off_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_turn_off_action(template_arg)
type = TurnOffAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
if CONF_TRANSITION_LENGTH in config:
for template_ in templatable(config[CONF_TRANSITION_LENGTH], args, uint32):
yield None
add(action.set_transition_length(template_))
yield action
CONF_LIGHT_TURN_ON = 'light.turn_on'
LIGHT_TURN_ON_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(LightState),
vol.Exclusive(CONF_TRANSITION_LENGTH, 'transformer'):
cv.templatable(cv.positive_time_period_milliseconds),
vol.Exclusive(CONF_FLASH_LENGTH, 'transformer'):
cv.templatable(cv.positive_time_period_milliseconds),
vol.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage),
vol.Optional(CONF_RED): cv.templatable(cv.percentage),
vol.Optional(CONF_GREEN): cv.templatable(cv.percentage),
vol.Optional(CONF_BLUE): cv.templatable(cv.percentage),
vol.Optional(CONF_WHITE): cv.templatable(cv.percentage),
vol.Optional(CONF_COLOR_TEMPERATURE): cv.templatable(cv.positive_float),
vol.Optional(CONF_EFFECT): cv.templatable(cv.string),
})
@ACTION_REGISTRY.register(CONF_LIGHT_TURN_ON, LIGHT_TURN_ON_ACTION_SCHEMA)
def light_turn_on_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_turn_on_action(template_arg)
type = TurnOnAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
if CONF_TRANSITION_LENGTH in config:
for template_ in templatable(config[CONF_TRANSITION_LENGTH], args, uint32):
yield None
add(action.set_transition_length(template_))
if CONF_FLASH_LENGTH in config:
for template_ in templatable(config[CONF_FLASH_LENGTH], args, uint32):
yield None
add(action.set_flash_length(template_))
if CONF_BRIGHTNESS in config:
for template_ in templatable(config[CONF_BRIGHTNESS], args, float_):
yield None
add(action.set_brightness(template_))
if CONF_RED in config:
for template_ in templatable(config[CONF_RED], args, float_):
yield None
add(action.set_red(template_))
if CONF_GREEN in config:
for template_ in templatable(config[CONF_GREEN], args, float_):
yield None
add(action.set_green(template_))
if CONF_BLUE in config:
for template_ in templatable(config[CONF_BLUE], args, float_):
yield None
add(action.set_blue(template_))
if CONF_WHITE in config:
for template_ in templatable(config[CONF_WHITE], args, float_):
yield None
add(action.set_white(template_))
if CONF_COLOR_TEMPERATURE in config:
for template_ in templatable(config[CONF_COLOR_TEMPERATURE], args, float_):
yield None
add(action.set_color_temperature(template_))
if CONF_EFFECT in config:
for template_ in templatable(config[CONF_EFFECT], args, std_string):
yield None
add(action.set_effect(template_))
yield action
def core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=True,
white_value=True):
ret = mqtt.build_hass_config(data, 'light', config, include_state=True, include_command=True)
if ret is None:
return None
ret['schema'] = 'json'
if brightness:
ret['brightness'] = True
if rgb:
ret['rgb'] = True
if color_temp:
ret['color_temp'] = True
if white_value:
ret['white_value'] = True
for effect in config.get(CONF_EFFECTS, []):
ret["effect"] = True
effects = ret.setdefault("effect_list", [])
effects.append(next(x for x in effect.values())[CONF_NAME])
return ret

View File

@@ -0,0 +1,28 @@
import voluptuous as vol
from esphome.components import light, output
import esphome.config_validation as cv
from esphome.const import CONF_EFFECTS, CONF_MAKE_ID, CONF_NAME, CONF_OUTPUT
from esphome.cpp_generator import get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.BinaryOutput),
vol.Optional(CONF_EFFECTS): light.validate_effects(light.BINARY_EFFECTS),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
for output_ in get_variable(config[CONF_OUTPUT]):
yield
rhs = App.make_binary_light(config[CONF_NAME], output_)
light_struct = variable(config[CONF_MAKE_ID], rhs)
light.setup_light(light_struct.Pstate, config)
setup_component(light_struct.Pstate, config)
def to_hass_config(data, config):
return light.core_to_hass_config(data, config, brightness=False, rgb=False, color_temp=False,
white_value=False)

View File

@@ -0,0 +1,42 @@
import voluptuous as vol
from esphome.components import light, output
from esphome.components.light.rgbww import validate_cold_white_colder, \
validate_color_temperature
import esphome.config_validation as cv
from esphome.const import CONF_COLD_WHITE, CONF_COLD_WHITE_COLOR_TEMPERATURE, \
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, \
CONF_NAME, CONF_WARM_WHITE, CONF_WARM_WHITE_COLOR_TEMPERATURE
from esphome.cpp_generator import get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
vol.Required(CONF_COLD_WHITE): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_WARM_WHITE): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): validate_color_temperature,
vol.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): validate_color_temperature,
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_EFFECTS): light.validate_effects(light.MONOCHROMATIC_EFFECTS),
}).extend(cv.COMPONENT_SCHEMA.schema), validate_cold_white_colder)
def to_code(config):
for cold_white in get_variable(config[CONF_COLD_WHITE]):
yield
for warm_white in get_variable(config[CONF_WARM_WHITE]):
yield
rhs = App.make_cwww_light(config[CONF_NAME], config[CONF_COLD_WHITE_COLOR_TEMPERATURE],
config[CONF_WARM_WHITE_COLOR_TEMPERATURE],
cold_white, warm_white)
light_struct = variable(config[CONF_MAKE_ID], rhs)
light.setup_light(light_struct.Pstate, config)
setup_component(light_struct.Pstate, config)
def to_hass_config(data, config):
return light.core_to_hass_config(data, config, brightness=True, rgb=False, color_temp=True,
white_value=False)

View File

@@ -0,0 +1,113 @@
import voluptuous as vol
from esphome import pins
from esphome.components import light
from esphome.components.power_supply import PowerSupplyComponent
import esphome.config_validation as cv
from esphome.const import CONF_CHIPSET, CONF_COLOR_CORRECT, CONF_DEFAULT_TRANSITION_LENGTH, \
CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, CONF_MAX_REFRESH_RATE, CONF_NAME, \
CONF_NUM_LEDS, CONF_PIN, CONF_POWER_SUPPLY, CONF_RGB_ORDER
from esphome.cpp_generator import RawExpression, TemplateArguments, add, get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Application
TYPES = [
'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',
]
RGB_ORDERS = [
'RGB',
'RBG',
'GRB',
'GBR',
'BRG',
'BGR',
]
def validate(value):
if value[CONF_CHIPSET] == 'NEOPIXEL' and CONF_RGB_ORDER in value:
raise vol.Invalid("NEOPIXEL doesn't support RGB order")
return value
MakeFastLEDLight = Application.struct('MakeFastLEDLight')
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(light.AddressableLightState),
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeFastLEDLight),
vol.Required(CONF_CHIPSET): cv.one_of(*TYPES, upper=True),
vol.Required(CONF_PIN): pins.output_pin,
vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
vol.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
vol.Optional(CONF_RGB_ORDER): cv.one_of(*RGB_ORDERS, upper=True),
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
vol.Optional(CONF_COLOR_CORRECT): vol.All([cv.percentage], vol.Length(min=3, max=3)),
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS),
}).extend(cv.COMPONENT_SCHEMA.schema), validate)
def to_code(config):
rhs = App.make_fast_led_light(config[CONF_NAME])
make = variable(config[CONF_MAKE_ID], rhs)
fast_led = make.Pfast_led
rgb_order = None
if CONF_RGB_ORDER in config:
rgb_order = RawExpression(config[CONF_RGB_ORDER])
template_args = TemplateArguments(RawExpression(config[CONF_CHIPSET]),
config[CONF_PIN], rgb_order)
add(fast_led.add_leds(template_args, config[CONF_NUM_LEDS]))
if CONF_MAX_REFRESH_RATE in config:
add(fast_led.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
if CONF_POWER_SUPPLY in config:
for power_supply in get_variable(config[CONF_POWER_SUPPLY]):
yield
add(fast_led.set_power_supply(power_supply))
if CONF_COLOR_CORRECT in config:
r, g, b = config[CONF_COLOR_CORRECT]
add(fast_led.set_correction(r, g, b))
light.setup_light(make.Pstate, config)
setup_component(fast_led, config)
REQUIRED_BUILD_FLAGS = '-DUSE_FAST_LED_LIGHT'
LIB_DEPS = 'FastLED@3.2.0'
def to_hass_config(data, config):
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False,
white_value=False)

View File

@@ -0,0 +1,93 @@
import voluptuous as vol
from esphome import pins
from esphome.components import light
from esphome.components.power_supply import PowerSupplyComponent
import esphome.config_validation as cv
from esphome.const import CONF_CHIPSET, CONF_CLOCK_PIN, CONF_COLOR_CORRECT, CONF_DATA_PIN, \
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, \
CONF_MAX_REFRESH_RATE, CONF_NAME, CONF_NUM_LEDS, CONF_POWER_SUPPLY, CONF_RGB_ORDER
from esphome.cpp_generator import RawExpression, TemplateArguments, add, get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Application
CHIPSETS = [
'LPD8806',
'WS2801',
'WS2803',
'SM16716',
'P9813',
'APA102',
'SK9822',
'DOTSTAR',
]
RGB_ORDERS = [
'RGB',
'RBG',
'GRB',
'GBR',
'BRG',
'BGR',
]
MakeFastLEDLight = Application.struct('MakeFastLEDLight')
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(light.AddressableLightState),
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeFastLEDLight),
vol.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
vol.Required(CONF_DATA_PIN): pins.output_pin,
vol.Required(CONF_CLOCK_PIN): pins.output_pin,
vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
vol.Optional(CONF_RGB_ORDER): cv.one_of(*RGB_ORDERS, upper=True),
vol.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
vol.Optional(CONF_COLOR_CORRECT): vol.All([cv.percentage], vol.Length(min=3, max=3)),
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
rhs = App.make_fast_led_light(config[CONF_NAME])
make = variable(config[CONF_MAKE_ID], rhs)
fast_led = make.Pfast_led
rgb_order = None
if CONF_RGB_ORDER in config:
rgb_order = RawExpression(config[CONF_RGB_ORDER])
template_args = TemplateArguments(RawExpression(config[CONF_CHIPSET]),
config[CONF_DATA_PIN],
config[CONF_CLOCK_PIN],
rgb_order)
add(fast_led.add_leds(template_args, config[CONF_NUM_LEDS]))
if CONF_MAX_REFRESH_RATE in config:
add(fast_led.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
if CONF_POWER_SUPPLY in config:
for power_supply in get_variable(config[CONF_POWER_SUPPLY]):
yield
add(fast_led.set_power_supply(power_supply))
if CONF_COLOR_CORRECT in config:
r, g, b = config[CONF_COLOR_CORRECT]
add(fast_led.set_correction(r, g, b))
light.setup_light(make.Pstate, config)
setup_component(fast_led, config)
REQUIRED_BUILD_FLAGS = '-DUSE_FAST_LED_LIGHT'
LIB_DEPS = 'FastLED@3.2.0'
def to_hass_config(data, config):
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False,
white_value=False)

View File

@@ -0,0 +1,31 @@
import voluptuous as vol
from esphome.components import light, output
import esphome.config_validation as cv
from esphome.const import CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, \
CONF_MAKE_ID, CONF_NAME, CONF_OUTPUT
from esphome.cpp_generator import get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.FloatOutput),
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_EFFECTS): light.validate_effects(light.MONOCHROMATIC_EFFECTS),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
for output_ in get_variable(config[CONF_OUTPUT]):
yield
rhs = App.make_monochromatic_light(config[CONF_NAME], output_)
light_struct = variable(config[CONF_MAKE_ID], rhs)
light.setup_light(light_struct.Pstate, config)
setup_component(light_struct.Pstate, config)
def to_hass_config(data, config):
return light.core_to_hass_config(data, config, brightness=True, rgb=False, color_temp=False,
white_value=False)

View File

@@ -0,0 +1,195 @@
import voluptuous as vol
from esphome import pins
from esphome.components import light
from esphome.components.light import AddressableLight
from esphome.components.power_supply import PowerSupplyComponent
import esphome.config_validation as cv
from esphome.const import CONF_CLOCK_PIN, CONF_COLOR_CORRECT, CONF_DATA_PIN, \
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_MAKE_ID, CONF_METHOD, \
CONF_NAME, CONF_NUM_LEDS, CONF_PIN, CONF_POWER_SUPPLY, CONF_TYPE, CONF_VARIANT
from esphome.core import CORE
from esphome.cpp_generator import TemplateArguments, add, get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Application, Component, global_ns
NeoPixelBusLightOutputBase = light.light_ns.class_('NeoPixelBusLightOutputBase', Component,
AddressableLight)
ESPNeoPixelOrder = light.light_ns.namespace('ESPNeoPixelOrder')
def validate_type(value):
value = cv.string(value).upper()
if 'R' not in value:
raise vol.Invalid("Must have R in type")
if 'G' not in value:
raise vol.Invalid("Must have G in type")
if 'B' not in value:
raise vol.Invalid("Must have B in type")
rest = set(value) - set('RGBW')
if rest:
raise vol.Invalid("Type has invalid color: {}".format(', '.join(rest)))
if len(set(value)) != len(value):
raise vol.Invalid("Type has duplicate color!")
return 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'
return cv.one_of(*VARIANTS)(value)
def validate_method(value):
if value is None:
if CORE.is_esp32:
return 'ESP32_I2S_1'
if CORE.is_esp8266:
return 'ESP8266_DMA'
raise NotImplementedError
if CORE.is_esp32:
return cv.one_of(*ESP32_METHODS, upper=True, space='_')(value)
if CORE.is_esp8266:
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)),
}
if CORE.is_esp8266:
method_pins['BIT_BANG'] = list(range(0, 16))
elif CORE.is_esp32:
method_pins['BIT_BANG'] = list(range(0, 32))
pins_ = method_pins[method]
for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN):
if opt in value and value[opt] not in pins_:
raise vol.Invalid("Method {} only supports pin(s) {}".format(
method, ', '.join('GPIO{}'.format(x) for x in pins_)
), path=[CONF_METHOD])
return value
VARIANTS = {
'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',
}
ESP32_METHODS = {
'ESP32_I2S_0': 'NeoEsp32I2s0{}Method',
'ESP32_I2S_1': 'NeoEsp32I2s1{}Method',
'BIT_BANG': 'NeoEsp32BitBang{}Method',
}
def format_method(config):
variant = VARIANTS[config[CONF_VARIANT]]
method = config[CONF_METHOD]
if CORE.is_esp8266:
return ESP8266_METHODS[method].format(variant)
if CORE.is_esp32:
return ESP32_METHODS[method].format(variant)
raise NotImplementedError
def validate(config):
if CONF_PIN in config:
if CONF_CLOCK_PIN in config or CONF_DATA_PIN in config:
raise vol.Invalid("Cannot specify both 'pin' and 'clock_pin'+'data_pin'")
return config
if CONF_CLOCK_PIN in config:
if CONF_DATA_PIN not in config:
raise vol.Invalid("If you give clock_pin, you must also specify data_pin")
return config
raise vol.Invalid("Must specify at least one of 'pin' or 'clock_pin'+'data_pin'")
MakeNeoPixelBusLight = Application.struct('MakeNeoPixelBusLight')
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(light.AddressableLightState),
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakeNeoPixelBusLight),
vol.Optional(CONF_TYPE, default='GRB'): validate_type,
vol.Optional(CONF_VARIANT, default='800KBPS'): validate_variant,
vol.Optional(CONF_METHOD, default=None): validate_method,
vol.Optional(CONF_PIN): pins.output_pin,
vol.Optional(CONF_CLOCK_PIN): pins.output_pin,
vol.Optional(CONF_DATA_PIN): pins.output_pin,
vol.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
vol.Optional(CONF_COLOR_CORRECT): vol.All([cv.percentage], vol.Length(min=3, max=4)),
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS),
}).extend(cv.COMPONENT_SCHEMA.schema), validate, validate_method_pin)
def to_code(config):
type_ = config[CONF_TYPE]
has_white = 'W' in type_
if has_white:
func = App.make_neo_pixel_bus_rgbw_light
color_feat = global_ns.NeoRgbwFeature
else:
func = App.make_neo_pixel_bus_rgb_light
color_feat = global_ns.NeoRgbFeature
template = TemplateArguments(getattr(global_ns, format_method(config)), color_feat)
rhs = func(template, config[CONF_NAME])
make = variable(config[CONF_MAKE_ID], rhs, type=MakeNeoPixelBusLight.template(template))
output = make.Poutput
if CONF_PIN in config:
add(output.add_leds(config[CONF_NUM_LEDS], config[CONF_PIN]))
else:
add(output.add_leds(config[CONF_NUM_LEDS], config[CONF_CLOCK_PIN], config[CONF_DATA_PIN]))
add(output.set_pixel_order(getattr(ESPNeoPixelOrder, type_)))
if CONF_POWER_SUPPLY in config:
for power_supply in get_variable(config[CONF_POWER_SUPPLY]):
yield
add(output.set_power_supply(power_supply))
if CONF_COLOR_CORRECT in config:
add(output.set_correction(*config[CONF_COLOR_CORRECT]))
light.setup_light(make.Pstate, config)
setup_component(output, config)
REQUIRED_BUILD_FLAGS = '-DUSE_NEO_PIXEL_BUS_LIGHT'
LIB_DEPS = 'NeoPixelBus@2.4.1'
def to_hass_config(data, config):
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False,
white_value='W' in config[CONF_TYPE])

View File

@@ -0,0 +1,52 @@
import voluptuous as vol
from esphome.components import light
from esphome.components.light import AddressableLight
import esphome.config_validation as cv
from esphome.const import CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_FROM, CONF_ID, \
CONF_MAKE_ID, CONF_NAME, CONF_SEGMENTS, CONF_TO
from esphome.cpp_generator import get_variable, variable
from esphome.cpp_types import App, Application
AddressableSegment = light.light_ns.class_('AddressableSegment')
PartitionLightOutput = light.light_ns.class_('PartitionLightOutput', AddressableLight)
MakePartitionLight = Application.struct('MakePartitionLight')
def validate_from_to(value):
if value[CONF_FROM] > value[CONF_TO]:
raise vol.Invalid(u"From ({}) must not be larger than to ({})"
u"".format(value[CONF_FROM], value[CONF_TO]))
return value
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(light.AddressableLightState),
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(MakePartitionLight),
vol.Required(CONF_SEGMENTS): vol.All(cv.ensure_list({
vol.Required(CONF_ID): cv.use_variable_id(light.AddressableLightState),
vol.Required(CONF_FROM): cv.positive_int,
vol.Required(CONF_TO): cv.positive_int,
}, validate_from_to), vol.Length(min=1)),
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_EFFECTS): light.validate_effects(light.ADDRESSABLE_EFFECTS),
}))
def to_code(config):
segments = []
for conf in config[CONF_SEGMENTS]:
for var in get_variable(conf[CONF_ID]):
yield
segments.append(AddressableSegment(var, conf[CONF_FROM],
conf[CONF_TO] - conf[CONF_FROM] + 1))
rhs = App.make_partition_light(config[CONF_NAME], segments)
make = variable(config[CONF_MAKE_ID], rhs)
light.setup_light(make.Pstate, config)
def to_hass_config(data, config):
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False)

View File

@@ -0,0 +1,37 @@
import voluptuous as vol
from esphome.components import light, output
import esphome.config_validation as cv
from esphome.const import CONF_BLUE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, \
CONF_GAMMA_CORRECT, CONF_GREEN, CONF_MAKE_ID, CONF_NAME, CONF_RED
from esphome.cpp_generator import get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
vol.Required(CONF_RED): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_GREEN): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_BLUE): cv.use_variable_id(output.FloatOutput),
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_EFFECTS): light.validate_effects(light.RGB_EFFECTS),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
for red in get_variable(config[CONF_RED]):
yield
for green in get_variable(config[CONF_GREEN]):
yield
for blue in get_variable(config[CONF_BLUE]):
yield
rhs = App.make_rgb_light(config[CONF_NAME], red, green, blue)
light_struct = variable(config[CONF_MAKE_ID], rhs)
light.setup_light(light_struct.Pstate, config)
setup_component(light_struct.Pstate, config)
def to_hass_config(data, config):
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False,
white_value=False)

View File

@@ -0,0 +1,40 @@
import voluptuous as vol
from esphome.components import light, output
import esphome.config_validation as cv
from esphome.const import CONF_BLUE, CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, \
CONF_GAMMA_CORRECT, CONF_GREEN, CONF_MAKE_ID, CONF_NAME, CONF_RED, CONF_WHITE
from esphome.cpp_generator import get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
vol.Required(CONF_RED): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_GREEN): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_BLUE): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_WHITE): cv.use_variable_id(output.FloatOutput),
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_EFFECTS): light.validate_effects(light.RGB_EFFECTS),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
for red in get_variable(config[CONF_RED]):
yield
for green in get_variable(config[CONF_GREEN]):
yield
for blue in get_variable(config[CONF_BLUE]):
yield
for white in get_variable(config[CONF_WHITE]):
yield
rhs = App.make_rgbw_light(config[CONF_NAME], red, green, blue, white)
light_struct = variable(config[CONF_MAKE_ID], rhs)
light.setup_light(light_struct.Pstate, config)
setup_component(light_struct.Pstate, config)
def to_hass_config(data, config):
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=False,
white_value=True)

View File

@@ -0,0 +1,70 @@
import voluptuous as vol
from esphome.components import light, output
import esphome.config_validation as cv
from esphome.const import CONF_BLUE, CONF_COLD_WHITE, CONF_COLD_WHITE_COLOR_TEMPERATURE, \
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_GREEN, CONF_MAKE_ID, \
CONF_NAME, CONF_RED, CONF_WARM_WHITE, CONF_WARM_WHITE_COLOR_TEMPERATURE
from esphome.cpp_generator import get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App
def validate_color_temperature(value):
try:
val = cv.float_with_unit('Color Temperature', 'mireds')(value)
except vol.Invalid:
val = 1000000.0 / cv.float_with_unit('Color Temperature', 'K')(value)
if val < 0:
raise vol.Invalid("Color temperature cannot be negative")
return val
def validate_cold_white_colder(value):
cw = value[CONF_COLD_WHITE_COLOR_TEMPERATURE]
ww = value[CONF_WARM_WHITE_COLOR_TEMPERATURE]
if cw > ww:
raise vol.Invalid("Cold white color temperature cannot be higher than warm white")
if cw == ww:
raise vol.Invalid("Cold white color temperature cannot be the same as warm white")
return value
PLATFORM_SCHEMA = cv.nameable(light.LIGHT_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(light.MakeLight),
vol.Required(CONF_RED): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_GREEN): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_BLUE): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_COLD_WHITE): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_WARM_WHITE): cv.use_variable_id(output.FloatOutput),
vol.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): validate_color_temperature,
vol.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): validate_color_temperature,
vol.Optional(CONF_GAMMA_CORRECT): cv.positive_float,
vol.Optional(CONF_DEFAULT_TRANSITION_LENGTH): cv.positive_time_period_milliseconds,
vol.Optional(CONF_EFFECTS): light.validate_effects(light.RGB_EFFECTS),
}).extend(cv.COMPONENT_SCHEMA.schema), validate_cold_white_colder)
def to_code(config):
for red in get_variable(config[CONF_RED]):
yield
for green in get_variable(config[CONF_GREEN]):
yield
for blue in get_variable(config[CONF_BLUE]):
yield
for cold_white in get_variable(config[CONF_COLD_WHITE]):
yield
for warm_white in get_variable(config[CONF_WARM_WHITE]):
yield
rhs = App.make_rgbww_light(config[CONF_NAME], config[CONF_COLD_WHITE_COLOR_TEMPERATURE],
config[CONF_WARM_WHITE_COLOR_TEMPERATURE],
red, green, blue, cold_white, warm_white)
light_struct = variable(config[CONF_MAKE_ID], rhs)
light.setup_light(light_struct.Pstate, config)
setup_component(light_struct.Pstate, config)
def to_hass_config(data, config):
return light.core_to_hass_config(data, config, brightness=True, rgb=True, color_temp=True,
white_value=True)

View File

@@ -0,0 +1,184 @@
import re
import voluptuous as vol
from esphome.automation import ACTION_REGISTRY, LambdaAction
import esphome.config_validation as cv
from esphome.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_HARDWARE_UART, CONF_ID, \
CONF_LEVEL, CONF_LOGS, CONF_TAG, CONF_TX_BUFFER_SIZE
from esphome.core import CORE, EsphomeError, Lambda
from esphome.cpp_generator import Pvariable, RawExpression, add, process_lambda, statement
from esphome.cpp_types import App, Component, esphome_ns, global_ns, void
from esphome.py_compat import text_type
LOG_LEVELS = {
'NONE': global_ns.ESPHOME_LOG_LEVEL_NONE,
'ERROR': global_ns.ESPHOME_LOG_LEVEL_ERROR,
'WARN': global_ns.ESPHOME_LOG_LEVEL_WARN,
'INFO': global_ns.ESPHOME_LOG_LEVEL_INFO,
'DEBUG': global_ns.ESPHOME_LOG_LEVEL_DEBUG,
'VERBOSE': global_ns.ESPHOME_LOG_LEVEL_VERBOSE,
'VERY_VERBOSE': global_ns.ESPHOME_LOG_LEVEL_VERY_VERBOSE,
}
LOG_LEVEL_TO_ESP_LOG = {
'ERROR': global_ns.ESP_LOGE,
'WARN': global_ns.ESP_LOGW,
'INFO': global_ns.ESP_LOGI,
'DEBUG': global_ns.ESP_LOGD,
'VERBOSE': global_ns.ESP_LOGV,
'VERY_VERBOSE': global_ns.ESP_LOGVV,
}
LOG_LEVEL_SEVERITY = ['NONE', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'VERBOSE', 'VERY_VERBOSE']
UART_SELECTION_ESP32 = ['UART0', 'UART1', 'UART2']
UART_SELECTION_ESP8266 = ['UART0', 'UART0_SWAP', 'UART1']
HARDWARE_UART_TO_UART_SELECTION = {
'UART0': global_ns.UART_SELECTION_UART0,
'UART0_SWAP': global_ns.UART_SELECTION_UART0_SWAP,
'UART1': global_ns.UART_SELECTION_UART1,
'UART2': global_ns.UART_SELECTION_UART2,
}
HARDWARE_UART_TO_SERIAL = {
'UART0': 'Serial',
'UART0_SWAP': 'Serial',
'UART1': 'Serial1',
'UART2': 'Serial2',
}
# pylint: disable=invalid-name
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
def uart_selection(value):
if CORE.is_esp32:
return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value)
if CORE.is_esp8266:
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
raise NotImplementedError
def validate_local_no_higher_than_global(value):
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(u"The local log level {} for {} must be less severe than the "
u"global log level {}.".format(level, tag, global_level))
return value
LogComponent = esphome_ns.class_('LogComponent', Component)
CONFIG_SCHEMA = vol.All(cv.Schema({
cv.GenerateID(): cv.declare_variable_id(LogComponent),
vol.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
vol.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
vol.Optional(CONF_HARDWARE_UART, default='UART0'): uart_selection,
vol.Optional(CONF_LEVEL): is_log_level,
vol.Optional(CONF_LOGS): cv.Schema({
cv.string: is_log_level,
})
}), validate_local_no_higher_than_global)
def to_code(config):
rhs = App.init_log(config.get(CONF_BAUD_RATE),
config.get(CONF_TX_BUFFER_SIZE),
HARDWARE_UART_TO_UART_SELECTION[config.get(CONF_HARDWARE_UART)])
log = Pvariable(config[CONF_ID], rhs)
if CONF_LEVEL in config:
add(log.set_global_log_level(LOG_LEVELS[config[CONF_LEVEL]]))
for tag, level in config.get(CONF_LOGS, {}).items():
add(log.set_log_level(tag, LOG_LEVELS[level]))
def required_build_flags(config):
flags = []
if CONF_LEVEL in config:
flags.append(u'-DESPHOME_LOG_LEVEL={}'.format(str(LOG_LEVELS[config[CONF_LEVEL]])))
this_severity = LOG_LEVEL_SEVERITY.index(config[CONF_LEVEL])
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 = config.get(CONF_BAUD_RATE) != 0
if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose:
debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)]
flags.append(u"-DDEBUG_ESP_PORT={}".format(debug_serial_port))
flags.append(u"-DLWIP_DEBUG")
DEBUG_COMPONENTS = {
'HTTP_CLIENT',
'HTTP_SERVER',
'HTTP_UPDATE',
'OTA',
'SSL',
'TLS_MEM',
'UPDATER',
'WIFI',
}
for comp in DEBUG_COMPONENTS:
flags.append(u"-DDEBUG_ESP_{}".format(comp))
if CORE.is_esp32 and is_at_least_verbose:
flags.append('-DCORE_DEBUG_LEVEL=5')
if CORE.is_esp32 and is_at_least_very_verbose:
flags.append('-DENABLE_I2C_DEBUG_BUFFER')
return flags
def maybe_simple_message(schema):
def validator(value):
if isinstance(value, dict):
return cv.Schema(schema)(value)
return cv.Schema(schema)({CONF_FORMAT: value})
return validator
def validate_printf(value):
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
# pylint: disable=anomalous-backslash-in-string
cfmt = u"""\
( # start of capture group 1
% # literal "%"
(?: # first option
(?:[-+0 #]{0,5}) # optional flags
(?:\d+|\*)? # width
(?:\.(?:\d+|\*))? # precision
(?:h|l|ll|w|I|I32|I64)? # size
[cCdiouxXeEfgGaAnpsSZ] # type
) | # OR
%%) # literal "%%"
""" # noqa
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X)
if len(matches) != len(value[CONF_ARGS]):
raise vol.Invalid(u"Found {} printf-patterns ({}), but {} args were given!"
u"".format(len(matches), u', '.join(matches), len(value[CONF_ARGS])))
return value
CONF_LOGGER_LOG = 'logger.log'
LOGGER_LOG_ACTION_SCHEMA = vol.All(maybe_simple_message({
vol.Required(CONF_FORMAT): cv.string,
vol.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
vol.Optional(CONF_LEVEL, default="DEBUG"): cv.one_of(*LOG_LEVEL_TO_ESP_LOG, upper=True),
vol.Optional(CONF_TAG, default="main"): cv.string,
}), validate_printf)
@ACTION_REGISTRY.register(CONF_LOGGER_LOG, LOGGER_LOG_ACTION_SCHEMA)
def logger_log_action_to_code(config, action_id, template_arg, args):
esp_log = LOG_LEVEL_TO_ESP_LOG[config[CONF_LEVEL]]
args_ = [RawExpression(text_type(x)) for x in config[CONF_ARGS]]
text = text_type(statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
for lambda_ in process_lambda(Lambda(text), args, return_type=void):
yield None
rhs = LambdaAction.new(template_arg, lambda_)
type = LambdaAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)

View File

@@ -0,0 +1,35 @@
import voluptuous as vol
from esphome import pins
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID
from esphome.cpp_generator import Pvariable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, GPIOInputPin, GPIOOutputPin, io_ns, esphome_ns
DEPENDENCIES = ['i2c']
MULTI_CONF = True
MCP23017GPIOMode = esphome_ns.enum('MCP23017GPIOMode')
MCP23017_GPIO_MODES = {
'INPUT': MCP23017GPIOMode.MCP23017_INPUT,
'INPUT_PULLUP': MCP23017GPIOMode.MCP23017_INPUT_PULLUP,
'OUTPUT': MCP23017GPIOMode.MCP23017_OUTPUT,
}
MCP23017GPIOInputPin = io_ns.class_('MCP23017GPIOInputPin', GPIOInputPin)
MCP23017GPIOOutputPin = io_ns.class_('MCP23017GPIOOutputPin', GPIOOutputPin)
CONFIG_SCHEMA = cv.Schema({
vol.Required(CONF_ID): cv.declare_variable_id(pins.MCP23017),
vol.Optional(CONF_ADDRESS, default=0x20): cv.i2c_address,
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.make_mcp23017_component(config[CONF_ADDRESS])
var = Pvariable(config[CONF_ID], rhs)
setup_component(var, config)
BUILD_FLAGS = '-DUSE_MCP23017'

View File

@@ -0,0 +1,29 @@
import voluptuous as vol
from esphome.components import i2c, binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID
from esphome.cpp_generator import Pvariable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Component
DEPENDENCIES = ['i2c']
MULTI_CONF = True
CONF_MPR121_ID = 'mpr121_id'
MPR121Component = binary_sensor.binary_sensor_ns.class_('MPR121Component', Component, i2c.I2CDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(MPR121Component),
vol.Optional(CONF_ADDRESS): cv.i2c_address
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.make_mpr121(config.get(CONF_ADDRESS))
var = Pvariable(config[CONF_ID], rhs)
setup_component(var, config)
BUILD_FLAGS = '-DUSE_MPR121'

360
esphome/components/mqtt.py Normal file
View File

@@ -0,0 +1,360 @@
from collections import OrderedDict
import re
import voluptuous as vol
from esphome import automation
from esphome.automation import ACTION_REGISTRY, CONDITION_REGISTRY, Condition
from esphome.components import logger
import esphome.config_validation as cv
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_ESPHOME, CONF_ID, CONF_INTERNAL, CONF_KEEPALIVE, CONF_LEVEL, CONF_LOG_TOPIC, \
CONF_MQTT, CONF_NAME, 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 EsphomeError
from esphome.cpp_generator import Pvariable, RawExpression, StructInitializer, TemplateArguments, \
add, get_variable, process_lambda, templatable
from esphome.cpp_types import Action, App, Component, JsonObjectConstRef, JsonObjectRef, \
Trigger, bool_, esphome_ns, optional, std_string, uint8, void
def validate_message_just_topic(value):
value = cv.publish_topic(value)
return MQTT_MESSAGE_BASE({CONF_TOPIC: value})
MQTT_MESSAGE_BASE = cv.Schema({
vol.Required(CONF_TOPIC): cv.publish_topic,
vol.Optional(CONF_QOS, default=0): cv.mqtt_qos,
vol.Optional(CONF_RETAIN, default=True): cv.boolean,
})
MQTT_MESSAGE_TEMPLATE_SCHEMA = vol.Any(None, MQTT_MESSAGE_BASE, validate_message_just_topic)
MQTT_MESSAGE_SCHEMA = vol.Any(None, MQTT_MESSAGE_BASE.extend({
vol.Required(CONF_PAYLOAD): cv.mqtt_payload,
}))
mqtt_ns = esphome_ns.namespace('mqtt')
MQTTMessage = mqtt_ns.struct('MQTTMessage')
MQTTClientComponent = mqtt_ns.class_('MQTTClientComponent', Component)
MQTTPublishAction = mqtt_ns.class_('MQTTPublishAction', Action)
MQTTPublishJsonAction = mqtt_ns.class_('MQTTPublishJsonAction', Action)
MQTTMessageTrigger = mqtt_ns.class_('MQTTMessageTrigger', Trigger.template(std_string))
MQTTJsonMessageTrigger = mqtt_ns.class_('MQTTJsonMessageTrigger',
Trigger.template(JsonObjectConstRef))
MQTTComponent = mqtt_ns.class_('MQTTComponent', Component)
MQTTConnectedCondition = mqtt_ns.class_('MQTTConnectedCondition', Condition)
def validate_config(value):
if CONF_PORT not in value:
parts = value[CONF_BROKER].split(u':')
if len(parts) == 2:
value[CONF_BROKER] = parts[0]
value[CONF_PORT] = cv.port(parts[1])
else:
value[CONF_PORT] = 1883
return value
def validate_fingerprint(value):
value = cv.string(value)
if re.match(r'^[0-9a-f]{40}$', value) is None:
raise vol.Invalid(u"fingerprint must be valid SHA1 hash")
return value
CONFIG_SCHEMA = vol.All(cv.Schema({
cv.GenerateID(): cv.declare_variable_id(MQTTClientComponent),
vol.Required(CONF_BROKER): cv.string_strict,
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_USERNAME, default=''): cv.string,
vol.Optional(CONF_PASSWORD, default=''): cv.string,
vol.Optional(CONF_CLIENT_ID): vol.All(cv.string, vol.Length(max=23)),
vol.Optional(CONF_DISCOVERY): vol.Any(cv.boolean, cv.one_of("CLEAN", upper=True)),
vol.Optional(CONF_DISCOVERY_RETAIN): cv.boolean,
vol.Optional(CONF_DISCOVERY_PREFIX): cv.publish_topic,
vol.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA,
vol.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA,
vol.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA,
vol.Optional(CONF_TOPIC_PREFIX): cv.publish_topic,
vol.Optional(CONF_LOG_TOPIC): vol.Any(None, MQTT_MESSAGE_BASE.extend({
vol.Optional(CONF_LEVEL): logger.is_log_level,
}), validate_message_just_topic),
vol.Optional(CONF_SSL_FINGERPRINTS): vol.All(cv.only_on_esp8266,
cv.ensure_list(validate_fingerprint)),
vol.Optional(CONF_KEEPALIVE): cv.positive_time_period_seconds,
vol.Optional(CONF_REBOOT_TIMEOUT): cv.positive_time_period_milliseconds,
vol.Optional(CONF_ON_MESSAGE): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MQTTMessageTrigger),
vol.Required(CONF_TOPIC): cv.subscribe_topic,
vol.Optional(CONF_QOS): cv.mqtt_qos,
vol.Optional(CONF_PAYLOAD): cv.string_strict,
}),
vol.Optional(CONF_ON_JSON_MESSAGE): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MQTTJsonMessageTrigger),
vol.Required(CONF_TOPIC): cv.subscribe_topic,
vol.Optional(CONF_QOS, default=0): cv.mqtt_qos,
}),
}), validate_config)
def exp_mqtt_message(config):
if config is None:
return optional(TemplateArguments(MQTTMessage))
exp = StructInitializer(
MQTTMessage,
('topic', config[CONF_TOPIC]),
('payload', config.get(CONF_PAYLOAD, "")),
('qos', config[CONF_QOS]),
('retain', config[CONF_RETAIN])
)
return exp
def to_code(config):
rhs = App.init_mqtt(config[CONF_BROKER], config[CONF_PORT],
config[CONF_USERNAME], config[CONF_PASSWORD])
mqtt = Pvariable(config[CONF_ID], rhs)
discovery = config.get(CONF_DISCOVERY, True)
discovery_retain = config.get(CONF_DISCOVERY_RETAIN, True)
discovery_prefix = config.get(CONF_DISCOVERY_PREFIX, 'homeassistant')
if not discovery:
add(mqtt.disable_discovery())
elif discovery == "CLEAN":
add(mqtt.set_discovery_info(discovery_prefix, discovery_retain, True))
elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config:
add(mqtt.set_discovery_info(discovery_prefix, discovery_retain))
if CONF_TOPIC_PREFIX in config:
add(mqtt.set_topic_prefix(config[CONF_TOPIC_PREFIX]))
if CONF_BIRTH_MESSAGE in config:
birth_message = config[CONF_BIRTH_MESSAGE]
if not birth_message:
add(mqtt.disable_birth_message())
else:
add(mqtt.set_birth_message(exp_mqtt_message(birth_message)))
if CONF_WILL_MESSAGE in config:
will_message = config[CONF_WILL_MESSAGE]
if not will_message:
add(mqtt.disable_last_will())
else:
add(mqtt.set_last_will(exp_mqtt_message(will_message)))
if CONF_SHUTDOWN_MESSAGE in config:
shutdown_message = config[CONF_SHUTDOWN_MESSAGE]
if not shutdown_message:
add(mqtt.disable_shutdown_message())
else:
add(mqtt.set_shutdown_message(exp_mqtt_message(shutdown_message)))
if CONF_CLIENT_ID in config:
add(mqtt.set_client_id(config[CONF_CLIENT_ID]))
if CONF_LOG_TOPIC in config:
log_topic = config[CONF_LOG_TOPIC]
if not log_topic:
add(mqtt.disable_log_message())
else:
add(mqtt.set_log_message_template(exp_mqtt_message(log_topic)))
if CONF_LEVEL in log_topic:
add(mqtt.set_log_level(logger.LOG_LEVELS[log_topic[CONF_LEVEL]]))
if CONF_SSL_FINGERPRINTS in config:
for fingerprint in config[CONF_SSL_FINGERPRINTS]:
arr = [RawExpression("0x{}".format(fingerprint[i:i + 2])) for i in range(0, 40, 2)]
add(mqtt.add_ssl_fingerprint(arr))
if CONF_KEEPALIVE in config:
add(mqtt.set_keep_alive(config[CONF_KEEPALIVE]))
if CONF_REBOOT_TIMEOUT in config:
add(mqtt.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
for conf in config.get(CONF_ON_MESSAGE, []):
rhs = App.register_component(mqtt.make_message_trigger(conf[CONF_TOPIC]))
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
if CONF_QOS in conf:
add(trigger.set_qos(conf[CONF_QOS]))
if CONF_PAYLOAD in conf:
add(trigger.set_payload(conf[CONF_PAYLOAD]))
automation.build_automations(trigger, [(std_string, 'x')], conf)
for conf in config.get(CONF_ON_JSON_MESSAGE, []):
rhs = mqtt.make_json_message_trigger(conf[CONF_TOPIC], conf[CONF_QOS])
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs)
automation.build_automations(trigger, [(JsonObjectConstRef, 'x')], conf)
CONF_MQTT_PUBLISH = 'mqtt.publish'
MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema({
cv.GenerateID(): cv.use_variable_id(MQTTClientComponent),
vol.Required(CONF_TOPIC): cv.templatable(cv.publish_topic),
vol.Required(CONF_PAYLOAD): cv.templatable(cv.mqtt_payload),
vol.Optional(CONF_QOS): cv.templatable(cv.mqtt_qos),
vol.Optional(CONF_RETAIN): cv.templatable(cv.boolean),
})
@ACTION_REGISTRY.register(CONF_MQTT_PUBLISH, MQTT_PUBLISH_ACTION_SCHEMA)
def mqtt_publish_action_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_publish_action(template_arg)
type = MQTTPublishAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
for template_ in templatable(config[CONF_TOPIC], args, std_string):
yield None
add(action.set_topic(template_))
for template_ in templatable(config[CONF_PAYLOAD], args, std_string):
yield None
add(action.set_payload(template_))
if CONF_QOS in config:
for template_ in templatable(config[CONF_QOS], args, uint8):
yield
add(action.set_qos(template_))
if CONF_RETAIN in config:
for template_ in templatable(config[CONF_RETAIN], args, bool_):
yield None
add(action.set_retain(template_))
yield action
CONF_MQTT_PUBLISH_JSON = 'mqtt.publish_json'
MQTT_PUBLISH_JSON_ACTION_SCHEMA = cv.Schema({
cv.GenerateID(): cv.use_variable_id(MQTTClientComponent),
vol.Required(CONF_TOPIC): cv.templatable(cv.publish_topic),
vol.Required(CONF_PAYLOAD): cv.lambda_,
vol.Optional(CONF_QOS): cv.mqtt_qos,
vol.Optional(CONF_RETAIN): cv.boolean,
})
@ACTION_REGISTRY.register(CONF_MQTT_PUBLISH_JSON, MQTT_PUBLISH_JSON_ACTION_SCHEMA)
def mqtt_publish_json_action_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_publish_json_action(template_arg)
type = MQTTPublishJsonAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
for template_ in templatable(config[CONF_TOPIC], args, std_string):
yield None
add(action.set_topic(template_))
args_ = args + [(JsonObjectRef, 'root')]
for lambda_ in process_lambda(config[CONF_PAYLOAD], args_, return_type=void):
yield None
add(action.set_payload(lambda_))
if CONF_QOS in config:
add(action.set_qos(config[CONF_QOS]))
if CONF_RETAIN in config:
add(action.set_retain(config[CONF_RETAIN]))
yield action
def required_build_flags(config):
if CONF_SSL_FINGERPRINTS in config:
return '-DASYNC_TCP_SSL_ENABLED=1'
return None
def get_default_topic_for(data, component_type, name, suffix):
whitelist = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_'
sanitized_name = ''.join(x for x in name.lower().replace(' ', '_') if x in whitelist)
return '{}/{}/{}/{}'.format(data.topic_prefix, component_type,
sanitized_name, suffix)
def build_hass_config(data, component_type, config, include_state=True, include_command=True):
if config.get(CONF_INTERNAL, False):
return None
ret = OrderedDict()
ret['platform'] = 'mqtt'
ret['name'] = config[CONF_NAME]
if include_state:
default = get_default_topic_for(data, component_type, config[CONF_NAME], 'state')
ret['state_topic'] = config.get(CONF_STATE_TOPIC, default)
if include_command:
default = get_default_topic_for(data, component_type, config[CONF_NAME], 'command')
ret['command_topic'] = config.get(CONF_STATE_TOPIC, default)
avail = config.get(CONF_AVAILABILITY, data.availability)
if avail:
ret['availability_topic'] = avail[CONF_TOPIC]
payload_available = avail[CONF_PAYLOAD_AVAILABLE]
if payload_available != 'online':
ret['payload_available'] = payload_available
payload_not_available = avail[CONF_PAYLOAD_NOT_AVAILABLE]
if payload_not_available != 'offline':
ret['payload_not_available'] = payload_not_available
return ret
class GenerateHassConfigData(object):
def __init__(self, config):
if 'mqtt' not in config:
raise EsphomeError("Cannot generate Home Assistant MQTT config if MQTT is not "
"used!")
mqtt = config[CONF_MQTT]
self.topic_prefix = mqtt.get(CONF_TOPIC_PREFIX, config[CONF_ESPHOME][CONF_NAME])
birth_message = mqtt.get(CONF_BIRTH_MESSAGE)
if CONF_BIRTH_MESSAGE not in mqtt:
birth_message = {
CONF_TOPIC: self.topic_prefix + '/status',
CONF_PAYLOAD: 'online',
}
will_message = mqtt.get(CONF_WILL_MESSAGE)
if CONF_WILL_MESSAGE not in mqtt:
will_message = {
CONF_TOPIC: self.topic_prefix + '/status',
CONF_PAYLOAD: 'offline'
}
if not birth_message or not will_message:
self.availability = None
elif birth_message[CONF_TOPIC] != will_message[CONF_TOPIC]:
self.availability = None
else:
self.availability = {
CONF_TOPIC: birth_message[CONF_TOPIC],
CONF_PAYLOAD_AVAILABLE: birth_message[CONF_PAYLOAD],
CONF_PAYLOAD_NOT_AVAILABLE: will_message[CONF_PAYLOAD],
}
def setup_mqtt_component(obj, config):
if CONF_RETAIN in config:
add(obj.set_retain(config[CONF_RETAIN]))
if not config.get(CONF_DISCOVERY, True):
add(obj.disable_discovery())
if CONF_STATE_TOPIC in config:
add(obj.set_custom_state_topic(config[CONF_STATE_TOPIC]))
if CONF_COMMAND_TOPIC in config:
add(obj.set_custom_command_topic(config[CONF_COMMAND_TOPIC]))
if CONF_AVAILABILITY in config:
availability = config[CONF_AVAILABILITY]
if not availability:
add(obj.disable_availability())
else:
add(obj.set_availability(availability[CONF_TOPIC], availability[CONF_PAYLOAD_AVAILABLE],
availability[CONF_PAYLOAD_NOT_AVAILABLE]))
LIB_DEPS = 'AsyncMqttClient@0.8.2'
BUILD_FLAGS = '-DUSE_MQTT'
CONF_MQTT_CONNECTED = 'mqtt.connected'
MQTT_CONNECTED_CONDITION_SCHEMA = cv.Schema({})
@CONDITION_REGISTRY.register(CONF_MQTT_CONNECTED, MQTT_CONNECTED_CONDITION_SCHEMA)
def mqtt_connected_to_code(config, condition_id, template_arg, args):
rhs = MQTTConnectedCondition.new(template_arg)
type = MQTTConnectedCondition.template(template_arg)
yield Pvariable(condition_id, rhs, type=type)

View File

@@ -0,0 +1,46 @@
import voluptuous as vol
from esphome import pins
from esphome.components import output
import esphome.config_validation as cv
from esphome.const import (CONF_BIT_DEPTH, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_ID,
CONF_NUM_CHANNELS, CONF_NUM_CHIPS, CONF_UPDATE_ON_BOOT)
from esphome.cpp_generator import Pvariable, add
from esphome.cpp_helpers import gpio_output_pin_expression, setup_component
from esphome.cpp_types import App, Component
MY9231OutputComponent = output.output_ns.class_('MY9231OutputComponent', Component)
MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(MY9231OutputComponent),
vol.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
vol.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
vol.Optional(CONF_NUM_CHANNELS): vol.All(vol.Coerce(int),
vol.Range(3, 1020)),
vol.Optional(CONF_NUM_CHIPS): vol.All(vol.Coerce(int),
vol.Range(1, 255)),
vol.Optional(CONF_BIT_DEPTH): cv.one_of(8, 12, 14, 16, int=True),
vol.Optional(CONF_UPDATE_ON_BOOT): vol.Coerce(bool),
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
for di in gpio_output_pin_expression(config[CONF_DATA_PIN]):
yield
for dcki in gpio_output_pin_expression(config[CONF_CLOCK_PIN]):
yield
rhs = App.make_my9231_component(di, dcki)
my9231 = Pvariable(config[CONF_ID], rhs)
if CONF_NUM_CHANNELS in config:
add(my9231.set_num_channels(config[CONF_NUM_CHANNELS]))
if CONF_NUM_CHIPS in config:
add(my9231.set_num_chips(config[CONF_NUM_CHIPS]))
if CONF_BIT_DEPTH in config:
add(my9231.set_bit_depth(config[CONF_BIT_DEPTH]))
if CONF_UPDATE_ON_BOOT in config:
add(my9231.set_update(config[CONF_UPDATE_ON_BOOT]))
setup_component(my9231, config)
BUILD_FLAGS = '-DUSE_MY9231_OUTPUT'

57
esphome/components/ota.py Normal file
View File

@@ -0,0 +1,57 @@
import logging
import voluptuous as vol
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_OTA, CONF_PASSWORD, CONF_PORT, CONF_SAFE_MODE
from esphome.core import CORE
from esphome.cpp_generator import Pvariable, add
from esphome.cpp_types import App, Component, esphome_ns
_LOGGER = logging.getLogger(__name__)
OTAComponent = esphome_ns.class_('OTAComponent', Component)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(OTAComponent),
vol.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_PASSWORD): cv.string,
})
def to_code(config):
rhs = App.init_ota()
ota = Pvariable(config[CONF_ID], rhs)
if CONF_PASSWORD in config:
add(ota.set_auth_password(config[CONF_PASSWORD]))
if CONF_PORT in config:
add(ota.set_port(config[CONF_PORT]))
if config[CONF_SAFE_MODE]:
add(ota.start_safe_mode())
def get_port(config):
if CONF_PORT in config[CONF_OTA]:
return config[CONF_OTA][CONF_PORT]
if CORE.is_esp32:
return 3232
if CORE.is_esp8266:
return 8266
raise NotImplementedError
def get_auth(config):
return config[CONF_OTA].get(CONF_PASSWORD, '')
BUILD_FLAGS = '-DUSE_OTA'
REQUIRED_BUILD_FLAGS = '-DUSE_NEW_OTA'
def lib_deps(config):
if CORE.is_esp32:
return ['Update']
if CORE.is_esp8266:
return ['Hash']
raise NotImplementedError

View File

@@ -0,0 +1,114 @@
import voluptuous as vol
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
from esphome.components.power_supply import PowerSupplyComponent
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_INVERTED, CONF_LEVEL, CONF_MAX_POWER, \
CONF_MIN_POWER, CONF_POWER_SUPPLY
from esphome.core import CORE
from esphome.cpp_generator import Pvariable, add, get_variable, templatable
from esphome.cpp_types import Action, esphome_ns, float_
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
})
BINARY_OUTPUT_SCHEMA = cv.Schema({
vol.Optional(CONF_POWER_SUPPLY): cv.use_variable_id(PowerSupplyComponent),
vol.Optional(CONF_INVERTED): cv.boolean,
})
BINARY_OUTPUT_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(BINARY_OUTPUT_SCHEMA.schema)
FLOAT_OUTPUT_SCHEMA = BINARY_OUTPUT_SCHEMA.extend({
vol.Optional(CONF_MAX_POWER): cv.percentage,
vol.Optional(CONF_MIN_POWER): cv.percentage,
})
FLOAT_OUTPUT_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(FLOAT_OUTPUT_SCHEMA.schema)
output_ns = 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', Action)
TurnOnAction = output_ns.class_('TurnOnAction', Action)
SetLevelAction = output_ns.class_('SetLevelAction', Action)
def setup_output_platform_(obj, config, skip_power_supply=False):
if CONF_INVERTED in config:
add(obj.set_inverted(config[CONF_INVERTED]))
if not skip_power_supply and CONF_POWER_SUPPLY in config:
power_supply = None
for power_supply in get_variable(config[CONF_POWER_SUPPLY]):
yield
add(obj.set_power_supply(power_supply))
if CONF_MAX_POWER in config:
add(obj.set_max_power(config[CONF_MAX_POWER]))
if CONF_MIN_POWER in config:
add(obj.set_min_power(config[CONF_MIN_POWER]))
def setup_output_platform(obj, config, skip_power_supply=False):
CORE.add_job(setup_output_platform_, obj, config, skip_power_supply)
def register_output(var, config):
output_var = Pvariable(config[CONF_ID], var, has_side_effects=True)
CORE.add_job(setup_output_platform_, output_var, config)
BUILD_FLAGS = '-DUSE_OUTPUT'
CONF_OUTPUT_TURN_ON = 'output.turn_on'
OUTPUT_TURN_ON_ACTION = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(BinaryOutput),
})
@ACTION_REGISTRY.register(CONF_OUTPUT_TURN_ON, OUTPUT_TURN_ON_ACTION)
def output_turn_on_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_turn_on_action(template_arg)
type = TurnOnAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
CONF_OUTPUT_TURN_OFF = 'output.turn_off'
OUTPUT_TURN_OFF_ACTION = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(BinaryOutput)
})
@ACTION_REGISTRY.register(CONF_OUTPUT_TURN_OFF, OUTPUT_TURN_OFF_ACTION)
def output_turn_off_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_turn_off_action(template_arg)
type = TurnOffAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
CONF_OUTPUT_SET_LEVEL = 'output.set_level'
OUTPUT_SET_LEVEL_ACTION = cv.Schema({
vol.Required(CONF_ID): cv.use_variable_id(FloatOutput),
vol.Required(CONF_LEVEL): cv.templatable(cv.percentage),
})
@ACTION_REGISTRY.register(CONF_OUTPUT_SET_LEVEL, OUTPUT_SET_LEVEL_ACTION)
def output_set_level_to_code(config, action_id, template_arg, args):
for var in get_variable(config[CONF_ID]):
yield None
rhs = var.make_set_level_action(template_arg)
type = SetLevelAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
for template_ in templatable(config[CONF_LEVEL], args, float_):
yield None
add(action.set_level(template_))
yield action

View File

@@ -0,0 +1,58 @@
import voluptuous as vol
from esphome.components import output
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_OUTPUTS, CONF_TYPE
from esphome.cpp_generator import Pvariable, get_variable
from esphome.cpp_helpers import setup_component
BinaryCopyOutput = output.output_ns.class_('BinaryCopyOutput', output.BinaryOutput)
FloatCopyOutput = output.output_ns.class_('FloatCopyOutput', output.FloatOutput)
BINARY_SCHEMA = output.PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): cv.declare_variable_id(BinaryCopyOutput),
vol.Required(CONF_TYPE): 'binary',
vol.Required(CONF_OUTPUTS): cv.ensure_list(cv.use_variable_id(output.BinaryOutput)),
})
FLOAT_SCHEMA = output.PLATFORM_SCHEMA.extend({
vol.Required(CONF_ID): cv.declare_variable_id(FloatCopyOutput),
vol.Required(CONF_TYPE): 'float',
vol.Required(CONF_OUTPUTS): cv.ensure_list(cv.use_variable_id(output.FloatOutput)),
})
def validate_copy_output(value):
if not isinstance(value, dict):
raise vol.Invalid("Value must be dict")
type = cv.string_strict(value.get(CONF_TYPE, 'float')).lower()
value[CONF_TYPE] = type
if type == 'binary':
return BINARY_SCHEMA(value)
if type == 'float':
return FLOAT_SCHEMA(value)
raise vol.Invalid("type must either be binary or float, not {}!".format(type))
PLATFORM_SCHEMA = validate_copy_output
def to_code(config):
outputs = []
for out in config[CONF_OUTPUTS]:
for var in get_variable(out):
yield
outputs.append(var)
klass = {
'binary': BinaryCopyOutput,
'float': FloatCopyOutput,
}[config[CONF_TYPE]]
rhs = klass.new(outputs)
gpio = Pvariable(config[CONF_ID], rhs)
output.setup_output_platform(gpio, config)
setup_component(gpio, config)
BUILD_FLAGS = '-DUSE_COPY_OUTPUT'

Some files were not shown because too many files have changed in this diff Show More