2018-09-29 14:50:04 +02:00
import logging
2018-09-23 18:58:41 +02:00
import os
import re
2019-04-17 12:06:00 +02:00
import esphome . codegen as cg
2019-02-13 16:54:02 +01:00
import esphome . config_validation as cv
2021-07-26 11:10:56 +02:00
from esphome import automation , boards
2021-03-07 20:03:16 +01:00
from esphome . const import (
CONF_ARDUINO_VERSION ,
CONF_BOARD ,
CONF_BOARD_FLASH_MODE ,
CONF_BUILD_PATH ,
CONF_COMMENT ,
CONF_ESPHOME ,
CONF_INCLUDES ,
CONF_LIBRARIES ,
CONF_NAME ,
CONF_ON_BOOT ,
CONF_ON_LOOP ,
CONF_ON_SHUTDOWN ,
CONF_PLATFORM ,
CONF_PLATFORMIO_OPTIONS ,
CONF_PRIORITY ,
2021-06-09 03:04:00 +02:00
CONF_PROJECT ,
2021-03-07 20:03:16 +01:00
CONF_TRIGGER_ID ,
CONF_ESP8266_RESTORE_FROM_FLASH ,
ARDUINO_VERSION_ESP8266 ,
ARDUINO_VERSION_ESP32 ,
2021-06-09 03:04:00 +02:00
CONF_VERSION ,
2021-03-07 20:03:16 +01:00
ESP_PLATFORMS ,
)
2019-04-22 21:56:30 +02:00
from esphome . core import CORE , coroutine_with_priority
2019-05-29 19:30:35 +02:00
from esphome . helpers import copy_file_if_changed , walk_files
2018-09-23 18:58:41 +02:00
2018-09-29 14:50:04 +02:00
_LOGGER = logging . getLogger ( __name__ )
2021-03-07 20:03:16 +01:00
BUILD_FLASH_MODES = [ " qio " , " qout " , " dio " , " dout " ]
StartupTrigger = cg . esphome_ns . class_ (
" StartupTrigger " , cg . Component , automation . Trigger . template ( )
)
ShutdownTrigger = cg . esphome_ns . class_ (
" ShutdownTrigger " , cg . Component , automation . Trigger . template ( )
)
LoopTrigger = cg . esphome_ns . class_ (
" LoopTrigger " , cg . Component , automation . Trigger . template ( )
)
2018-09-23 18:58:41 +02:00
2021-03-07 20:03:16 +01:00
VERSION_REGEX = re . compile ( r " ^[0-9]+ \ .[0-9]+ \ .[0-9]+(?:[ab] \ d+)?$ " )
2018-09-23 18:58:41 +02:00
2021-03-17 21:22:48 +01:00
CONF_NAME_ADD_MAC_SUFFIX = " name_add_mac_suffix "
2018-09-23 18:58:41 +02:00
2021-07-26 11:10:56 +02:00
def validate_board ( value : str ) :
2018-12-05 21:22:06 +01:00
if CORE . is_esp8266 :
2021-07-26 11:10:56 +02:00
boardlist = boards . ESP8266_BOARD_PINS . keys ( )
2018-12-05 21:22:06 +01:00
elif CORE . is_esp32 :
2021-07-26 11:10:56 +02:00
boardlist = list ( boards . ESP32_BOARD_PINS . keys ( ) )
boardlist + = list ( boards . ESP32_C3_BOARD_PINS . keys ( ) )
2018-09-23 18:58:41 +02:00
else :
raise NotImplementedError
2021-07-26 11:10:56 +02:00
if value not in boardlist :
2021-03-07 20:03:16 +01:00
raise cv . Invalid (
2021-09-19 19:22:28 +02:00
f " Could not find board ' { value } ' . Valid boards are { ' , ' . join ( sorted ( boardlist ) ) } "
2021-03-07 20:03:16 +01:00
)
2018-09-23 18:58:41 +02:00
return value
2020-05-24 01:33:58 +02:00
validate_platform = cv . one_of ( * ESP_PLATFORMS , upper = True )
2018-09-23 18:58:41 +02:00
PLATFORMIO_ESP8266_LUT = {
2020-07-27 18:22:47 +02:00
* * ARDUINO_VERSION_ESP8266 ,
2021-05-23 23:24:00 +02:00
# Keep this in mind when updating the recommended version:
# * New framework historically have had some regressions, especially for WiFi, BLE and the
# bootloader system. The new version needs to be thoroughly validated before changing the
# recommended version as otherwise a bunch of devices could be bricked
# * The docker images need to be updated to ship the new recommended version, in order not
# to DDoS platformio servers.
2021-07-02 15:42:36 +02:00
# Update this file: https://github.com/esphome/esphome-docker-base/blob/main/platformio.ini
2021-03-07 20:03:16 +01:00
" RECOMMENDED " : ARDUINO_VERSION_ESP8266 [ " 2.7.4 " ] ,
" LATEST " : " espressif8266 " ,
" DEV " : ARDUINO_VERSION_ESP8266 [ " dev " ] ,
2018-09-23 18:58:41 +02:00
}
PLATFORMIO_ESP32_LUT = {
2020-07-27 18:22:47 +02:00
* * ARDUINO_VERSION_ESP32 ,
2021-05-23 23:24:00 +02:00
# See PLATFORMIO_ESP8266_LUT for considerations when changing the recommended version
2021-05-17 01:38:26 +02:00
" RECOMMENDED " : ARDUINO_VERSION_ESP32 [ " 1.0.6 " ] ,
2021-03-07 20:03:16 +01:00
" LATEST " : " espressif32 " ,
" DEV " : ARDUINO_VERSION_ESP32 [ " dev " ] ,
2018-09-23 18:58:41 +02:00
}
def validate_arduino_version ( value ) :
value = cv . string_strict ( value )
value_ = value . upper ( )
2018-12-05 21:22:06 +01:00
if CORE . is_esp8266 :
2021-03-07 20:03:16 +01:00
if (
VERSION_REGEX . match ( value ) is not None
and value_ not in PLATFORMIO_ESP8266_LUT
) :
raise cv . Invalid (
2021-09-19 19:22:28 +02:00
f " Unfortunately the arduino framework version ' { value } ' is unsupported at this time. You can override this by manually using espressif8266@<platformio version> "
2021-03-07 20:03:16 +01:00
)
2018-09-23 18:58:41 +02:00
if value_ in PLATFORMIO_ESP8266_LUT :
return PLATFORMIO_ESP8266_LUT [ value_ ]
return value
2019-01-02 14:11:11 +01:00
if CORE . is_esp32 :
2021-03-07 20:03:16 +01:00
if (
VERSION_REGEX . match ( value ) is not None
and value_ not in PLATFORMIO_ESP32_LUT
) :
raise cv . Invalid (
2021-09-19 19:22:28 +02:00
f " Unfortunately the arduino framework version ' { value } ' is unsupported at this time. You can override this by manually using espressif32@<platformio version> "
2021-03-07 20:03:16 +01:00
)
2018-09-23 18:58:41 +02:00
if value_ in PLATFORMIO_ESP32_LUT :
return PLATFORMIO_ESP32_LUT [ value_ ]
return value
2018-12-05 21:22:06 +01:00
raise NotImplementedError
2018-09-23 18:58:41 +02:00
2018-09-29 14:50:04 +02:00
def default_build_path ( ) :
2018-12-05 21:22:06 +01:00
return CORE . name
2018-09-29 14:50:04 +02:00
2021-03-07 20:03:16 +01:00
VALID_INCLUDE_EXTS = { " .h " , " .hpp " , " .tcc " , " .ino " , " .cpp " , " .c " }
2019-05-29 19:30:35 +02:00
def valid_include ( value ) :
try :
return cv . directory ( value )
except cv . Invalid :
pass
value = cv . file_ ( value )
_ , ext = os . path . splitext ( value )
if ext not in VALID_INCLUDE_EXTS :
2021-03-07 20:03:16 +01:00
raise cv . Invalid (
2021-09-19 19:22:28 +02:00
f " Include has invalid file extension { ext } - valid extensions are { ' , ' . join ( VALID_INCLUDE_EXTS ) } "
2021-03-07 20:03:16 +01:00
)
2019-05-29 19:30:35 +02:00
return value
2021-06-09 03:04:00 +02:00
def valid_project_name ( value : str ) :
if value . count ( " . " ) != 1 :
raise cv . Invalid ( " project name needs to have a namespace " )
value = value . replace ( " " , " _ " )
return value
2021-03-07 20:03:16 +01:00
CONFIG_SCHEMA = cv . Schema (
{
2021-08-10 14:14:42 +02:00
cv . Required ( CONF_NAME ) : cv . hostname ,
2021-03-07 20:03:16 +01:00
cv . Required ( CONF_PLATFORM ) : cv . one_of ( " ESP8266 " , " ESP32 " , upper = True ) ,
cv . Required ( CONF_BOARD ) : validate_board ,
cv . Optional ( CONF_COMMENT ) : cv . string ,
cv . Optional (
CONF_ARDUINO_VERSION , default = " recommended "
) : validate_arduino_version ,
cv . Optional ( CONF_BUILD_PATH , default = default_build_path ) : cv . string ,
cv . Optional ( CONF_PLATFORMIO_OPTIONS , default = { } ) : cv . Schema (
{
cv . string_strict : cv . Any ( [ cv . string ] , cv . string ) ,
}
) ,
cv . SplitDefault ( CONF_ESP8266_RESTORE_FROM_FLASH , esp8266 = False ) : cv . All (
cv . only_on_esp8266 , cv . boolean
) ,
cv . SplitDefault ( CONF_BOARD_FLASH_MODE , esp8266 = " dout " ) : cv . one_of (
* BUILD_FLASH_MODES , lower = True
) ,
cv . Optional ( CONF_ON_BOOT ) : automation . validate_automation (
{
cv . GenerateID ( CONF_TRIGGER_ID ) : cv . declare_id ( StartupTrigger ) ,
cv . Optional ( CONF_PRIORITY , default = 600.0 ) : cv . float_ ,
}
) ,
cv . Optional ( CONF_ON_SHUTDOWN ) : automation . validate_automation (
{
cv . GenerateID ( CONF_TRIGGER_ID ) : cv . declare_id ( ShutdownTrigger ) ,
}
) ,
cv . Optional ( CONF_ON_LOOP ) : automation . validate_automation (
{
cv . GenerateID ( CONF_TRIGGER_ID ) : cv . declare_id ( LoopTrigger ) ,
}
) ,
cv . Optional ( CONF_INCLUDES , default = [ ] ) : cv . ensure_list ( valid_include ) ,
cv . Optional ( CONF_LIBRARIES , default = [ ] ) : cv . ensure_list ( cv . string_strict ) ,
2021-03-17 21:22:48 +01:00
cv . Optional ( CONF_NAME_ADD_MAC_SUFFIX , default = False ) : cv . boolean ,
2021-06-09 03:04:00 +02:00
cv . Optional ( CONF_PROJECT ) : cv . Schema (
{
cv . Required ( CONF_NAME ) : cv . All ( cv . string_strict , valid_project_name ) ,
cv . Required ( CONF_VERSION ) : cv . string_strict ,
}
) ,
2021-03-07 20:03:16 +01:00
}
)
PRELOAD_CONFIG_SCHEMA = cv . Schema (
{
cv . Required ( CONF_NAME ) : cv . valid_name ,
cv . Required ( CONF_PLATFORM ) : validate_platform ,
} ,
extra = cv . ALLOW_EXTRA ,
)
PRELOAD_CONFIG_SCHEMA2 = PRELOAD_CONFIG_SCHEMA . extend (
{
cv . Required ( CONF_BOARD ) : validate_board ,
cv . Optional ( CONF_BUILD_PATH , default = default_build_path ) : cv . string ,
}
)
2019-04-22 21:56:30 +02:00
2018-09-23 18:58:41 +02:00
def preload_core_config ( config ) :
2021-03-07 20:03:16 +01:00
core_key = " esphome "
if " esphomeyaml " in config :
_LOGGER . warning (
" The esphomeyaml section has been renamed to esphome in 1.11.0. "
" Please replace ' esphomeyaml: ' in your configuration with ' esphome: ' . "
)
config [ CONF_ESPHOME ] = config . pop ( " esphomeyaml " )
core_key = " esphomeyaml "
2019-02-13 16:54:02 +01:00
if CONF_ESPHOME not in config :
2019-04-22 21:56:30 +02:00
raise cv . RequiredFieldInvalid ( " required key not provided " , CONF_ESPHOME )
with cv . prepend_path ( core_key ) :
out = PRELOAD_CONFIG_SCHEMA ( config [ CONF_ESPHOME ] )
CORE . name = out [ CONF_NAME ]
CORE . esp_platform = out [ CONF_PLATFORM ]
with cv . prepend_path ( core_key ) :
out2 = PRELOAD_CONFIG_SCHEMA2 ( config [ CONF_ESPHOME ] )
CORE . board = out2 [ CONF_BOARD ]
CORE . build_path = CORE . relative_config_path ( out2 [ CONF_BUILD_PATH ] )
2018-09-29 14:50:04 +02:00
2019-05-29 19:30:35 +02:00
def include_file ( path , basename ) :
parts = basename . split ( os . path . sep )
dst = CORE . relative_src_path ( * parts )
copy_file_if_changed ( path , dst )
_ , ext = os . path . splitext ( path )
2021-03-07 20:03:16 +01:00
if ext in [ " .h " , " .hpp " , " .tcc " ] :
2019-05-29 19:30:35 +02:00
# Header, add include statement
2019-12-07 18:28:55 +01:00
cg . add_global ( cg . RawStatement ( f ' #include " { basename } " ' ) )
2019-05-29 19:30:35 +02:00
2019-04-17 12:06:00 +02:00
@coroutine_with_priority ( - 1000.0 )
2021-05-17 07:14:15 +02:00
async def add_includes ( includes ) :
2019-04-17 12:06:00 +02:00
# Add includes at the very end, so that the included files can access global variables
for include in includes :
2019-04-22 21:56:30 +02:00
path = CORE . relative_config_path ( include )
2019-05-29 19:30:35 +02:00
if os . path . isdir ( path ) :
# Directory, copy tree
for p in walk_files ( path ) :
basename = os . path . relpath ( p , os . path . dirname ( path ) )
include_file ( p , basename )
else :
# Copy file
basename = os . path . basename ( path )
include_file ( path , basename )
2019-04-17 12:06:00 +02:00
2020-07-27 18:22:47 +02:00
@coroutine_with_priority ( - 1000.0 )
2021-05-17 07:14:15 +02:00
async def _esp8266_add_lwip_type ( ) :
2020-07-27 18:22:47 +02:00
# If any component has already set this, do not change it
2021-03-07 20:03:16 +01:00
if any (
flag . startswith ( " -DPIO_FRAMEWORK_ARDUINO_LWIP2_ " ) for flag in CORE . build_flags
) :
2020-07-27 18:22:47 +02:00
return
# Default for platformio is LWIP2_LOW_MEMORY with:
# - MSS=536
# - LWIP_FEATURES enabled
# - this only adds some optional features like IP incoming packet reassembly and NAPT
# see also:
# https://github.com/esp8266/Arduino/blob/master/tools/sdk/lwip2/include/lwipopts.h
# Instead we use LWIP2_HIGHER_BANDWIDTH_LOW_FLASH with:
# - MSS=1460
# - LWIP_FEATURES disabled (because we don't need them)
# Other projects like Tasmota & ESPEasy also use this
2021-03-07 20:03:16 +01:00
cg . add_build_flag ( " -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH " )
2020-07-27 18:22:47 +02:00
2021-01-08 20:11:42 +01:00
@coroutine_with_priority ( 30.0 )
2021-05-17 07:14:15 +02:00
async def _add_automations ( config ) :
2018-09-23 18:58:41 +02:00
for conf in config . get ( CONF_ON_BOOT , [ ] ) :
2019-04-17 12:06:00 +02:00
trigger = cg . new_Pvariable ( conf [ CONF_TRIGGER_ID ] , conf . get ( CONF_PRIORITY ) )
2021-05-17 07:14:15 +02:00
await cg . register_component ( trigger , conf )
await automation . build_automation ( trigger , [ ] , conf )
2018-09-23 18:58:41 +02:00
for conf in config . get ( CONF_ON_SHUTDOWN , [ ] ) :
2019-04-17 12:06:00 +02:00
trigger = cg . new_Pvariable ( conf [ CONF_TRIGGER_ID ] )
2021-05-17 07:14:15 +02:00
await cg . register_component ( trigger , conf )
await automation . build_automation ( trigger , [ ] , conf )
2018-09-23 18:58:41 +02:00
for conf in config . get ( CONF_ON_LOOP , [ ] ) :
2019-04-17 12:06:00 +02:00
trigger = cg . new_Pvariable ( conf [ CONF_TRIGGER_ID ] )
2021-05-17 07:14:15 +02:00
await cg . register_component ( trigger , conf )
await automation . build_automation ( trigger , [ ] , conf )
2019-04-17 12:06:00 +02:00
2021-01-08 20:11:42 +01:00
@coroutine_with_priority ( 100.0 )
2021-05-17 07:14:15 +02:00
async def to_code ( config ) :
2021-03-07 20:03:16 +01:00
cg . add_global ( cg . global_ns . namespace ( " esphome " ) . using )
cg . add (
2021-03-17 21:22:48 +01:00
cg . App . pre_setup (
config [ CONF_NAME ] ,
cg . RawExpression ( ' __DATE__ " , " __TIME__ ' ) ,
config [ CONF_NAME_ADD_MAC_SUFFIX ] ,
)
2021-03-07 20:03:16 +01:00
)
2021-01-08 20:11:42 +01:00
CORE . add_job ( _add_automations , config )
2020-07-27 18:22:47 +02:00
# Set LWIP build constants for ESP8266
if CORE . is_esp8266 :
CORE . add_job ( _esp8266_add_lwip_type )
2019-04-17 12:06:00 +02:00
2021-03-07 20:03:16 +01:00
cg . add_build_flag ( " -fno-exceptions " )
2019-04-17 12:06:00 +02:00
# Libraries
for lib in config [ CONF_LIBRARIES ] :
2021-03-07 20:03:16 +01:00
if " @ " in lib :
name , vers = lib . split ( " @ " , 1 )
2019-04-17 12:06:00 +02:00
cg . add_library ( name , vers )
2021-07-26 10:50:45 +02:00
elif " :// " in lib :
# Repository...
if " = " in lib :
name , repo = lib . split ( " = " , 1 )
cg . add_library ( name , None , repo )
else :
cg . add_library ( None , None , lib )
2019-04-17 12:06:00 +02:00
else :
cg . add_library ( lib , None )
2021-08-10 10:46:46 +02:00
if CORE . is_esp8266 :
# Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when
# out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make
# new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of
# a NULL pointer (so the stacktrace makes more sense), and for consistency with Arduino 3,
# which always aborts if exceptions are disabled.
# For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;`
cg . add_build_flag ( " -DNEW_OOM_ABORT " )
2021-03-07 20:03:16 +01:00
cg . add_build_flag ( " -Wno-unused-variable " )
cg . add_build_flag ( " -Wno-unused-but-set-variable " )
cg . add_build_flag ( " -Wno-sign-compare " )
2019-02-26 16:55:37 +01:00
if config . get ( CONF_ESP8266_RESTORE_FROM_FLASH , False ) :
2021-03-07 20:03:16 +01:00
cg . add_define ( " USE_ESP8266_PREFERENCES_FLASH " )
2019-04-17 12:06:00 +02:00
if config [ CONF_INCLUDES ] :
CORE . add_job ( add_includes , config [ CONF_INCLUDES ] )
2021-06-09 03:04:00 +02:00
2021-06-09 04:23:48 +02:00
cg . add_define ( " ESPHOME_BOARD " , CORE . board )
2021-06-09 03:04:00 +02:00
if CONF_PROJECT in config :
cg . add_define ( " ESPHOME_PROJECT_NAME " , config [ CONF_PROJECT ] [ CONF_NAME ] )
cg . add_define ( " ESPHOME_PROJECT_VERSION " , config [ CONF_PROJECT ] [ CONF_VERSION ] )