mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
Support for LibreTiny platform (RTL8710, BK7231 & other modules) (#3509)
Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl> Co-authored-by: Sam Neirinck <git@samneirinck.com> Co-authored-by: David Buezas <dbuezas@users.noreply.github.com> Co-authored-by: Stroe Andrei Catalin <catalin2402@gmail.com> Co-authored-by: Sam Neirinck <github@samneirinck.be> Co-authored-by: Péter Sárközi <xmisterhu@gmail.com> Co-authored-by: Hajo Noerenberg <hn@users.noreply.github.com>
This commit is contained in:
parent
22c0b0abaa
commit
a9630ac847
78 changed files with 6085 additions and 89 deletions
|
@ -42,6 +42,7 @@ esphome/components/bedjet/climate/* @jhansche
|
|||
esphome/components/bedjet/fan/* @jhansche
|
||||
esphome/components/bh1750/* @OttoWinter
|
||||
esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/bk72xx/* @kuba2k2
|
||||
esphome/components/bl0939/* @ziceva
|
||||
esphome/components/bl0940/* @tobias-
|
||||
esphome/components/bl0942/* @dbuezas
|
||||
|
@ -146,6 +147,8 @@ esphome/components/kuntze/* @ssieb
|
|||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ld2410/* @regevbr @sebcaps
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/libretiny/* @kuba2k2
|
||||
esphome/components/libretiny_pwm/* @kuba2k2
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
esphome/components/lock/* @esphome/core
|
||||
|
@ -234,6 +237,7 @@ esphome/components/rgbct/* @jesserockz
|
|||
esphome/components/rp2040/* @jesserockz
|
||||
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
|
||||
esphome/components/rp2040_pwm/* @jesserockz
|
||||
esphome/components/rtl87xx/* @kuba2k2
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||
esphome/components/scd4x/* @martgras @sjtrny
|
||||
|
|
|
@ -26,6 +26,8 @@ from esphome.const import (
|
|||
CONF_ESPHOME,
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
CONF_SUBSTITUTIONS,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_RTL87XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
|
@ -278,20 +280,25 @@ def upload_using_esptool(config, port):
|
|||
return run_esptool(115200)
|
||||
|
||||
|
||||
def upload_using_platformio(config, port):
|
||||
from esphome import platformio_api
|
||||
|
||||
upload_args = ["-t", "upload", "-t", "nobuild"]
|
||||
if port is not None:
|
||||
upload_args += ["--upload-port", port]
|
||||
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args)
|
||||
|
||||
|
||||
def upload_program(config, args, host):
|
||||
if get_port_type(host) == "SERIAL":
|
||||
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
||||
return upload_using_esptool(config, host)
|
||||
|
||||
if CORE.target_platform in (PLATFORM_RP2040):
|
||||
from esphome import platformio_api
|
||||
return upload_using_platformio(config, args.device)
|
||||
|
||||
upload_args = ["-t", "upload"]
|
||||
if args.device is not None:
|
||||
upload_args += ["--upload-port", args.device]
|
||||
return platformio_api.run_platformio_cli_run(
|
||||
config, CORE.verbose, *upload_args
|
||||
)
|
||||
if CORE.target_platform in (PLATFORM_BK72XX, PLATFORM_RTL87XX):
|
||||
return upload_using_platformio(config, host)
|
||||
|
||||
return 1 # Unknown target platform
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_INPUT
|
||||
from esphome.const import CONF_ANALOG, CONF_INPUT
|
||||
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
|
@ -166,8 +166,6 @@ def validate_adc_pin(value):
|
|||
return pins.internal_gpio_input_pin_schema(value)
|
||||
|
||||
if CORE.is_esp8266:
|
||||
from esphome.components.esp8266.gpio import CONF_ANALOG
|
||||
|
||||
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
|
||||
value
|
||||
)
|
||||
|
@ -184,4 +182,9 @@ def validate_adc_pin(value):
|
|||
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC")
|
||||
return pins.internal_gpio_input_pin_schema(value)
|
||||
|
||||
if CORE.is_libretiny:
|
||||
return pins.gpio_pin_schema(
|
||||
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
|
||||
)(value)
|
||||
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -92,13 +92,13 @@ extern "C"
|
|||
|
||||
void ADCSensor::dump_config() {
|
||||
LOG_SENSOR("", "ADC Sensor", this);
|
||||
#ifdef USE_ESP8266
|
||||
#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
ESP_LOGCONFIG(TAG, " Pin: VCC");
|
||||
#else
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
#endif
|
||||
#endif // USE_ESP8266
|
||||
#endif // USE_ESP8266 || USE_LIBRETINY
|
||||
|
||||
#ifdef USE_ESP32
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
|
@ -254,6 +254,15 @@ float ADCSensor::sample() {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
float ADCSensor::sample() {
|
||||
if (output_raw_) {
|
||||
return analogRead(this->pin_->get_pin()); // NOLINT
|
||||
}
|
||||
return analogReadVoltage(this->pin_->get_pin()) / 1000.0f; // NOLINT
|
||||
}
|
||||
#endif // USE_LIBRETINY
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
||||
#endif
|
||||
|
|
|
@ -1051,6 +1051,10 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
|||
resp.manufacturer = "Espressif";
|
||||
#elif defined(USE_RP2040)
|
||||
resp.manufacturer = "Raspberry Pi";
|
||||
#elif defined(USE_BK72XX)
|
||||
resp.manufacturer = "Beken";
|
||||
#elif defined(USE_RTL87XX)
|
||||
resp.manufacturer = "Realtek";
|
||||
#elif defined(USE_HOST)
|
||||
resp.manufacturer = "Host";
|
||||
#endif
|
||||
|
|
|
@ -8,15 +8,15 @@ CODEOWNERS = ["@OttoWinter"]
|
|||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
cv.only_with_arduino,
|
||||
cv.only_on(["esp32", "esp8266"]),
|
||||
cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]),
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(200.0)
|
||||
async def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
if CORE.is_esp32 or CORE.is_libretiny:
|
||||
# https://github.com/esphome/AsyncTCP/blob/master/library.json
|
||||
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
|
||||
cg.add_library("esphome/AsyncTCP-esphome", "2.0.1")
|
||||
elif CORE.is_esp8266:
|
||||
# https://github.com/esphome/ESPAsyncTCP
|
||||
cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3")
|
||||
|
|
51
esphome/components/bk72xx/__init__.py
Normal file
51
esphome/components/bk72xx/__init__.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
# This file was auto-generated by libretiny/generate_components.py
|
||||
# Do not modify its contents.
|
||||
# For custom pin validators, put validate_pin() or validate_usage()
|
||||
# in gpio.py file in this directory.
|
||||
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
# in schema.py file in this directory.
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import libretiny
|
||||
from esphome.components.libretiny.const import (
|
||||
COMPONENT_BK72XX,
|
||||
KEY_COMPONENT_DATA,
|
||||
KEY_LIBRETINY,
|
||||
LibreTinyComponent,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
from .boards import BK72XX_BOARDS, BK72XX_BOARD_PINS
|
||||
|
||||
CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = ["libretiny"]
|
||||
|
||||
COMPONENT_DATA = LibreTinyComponent(
|
||||
name=COMPONENT_BK72XX,
|
||||
boards=BK72XX_BOARDS,
|
||||
board_pins=BK72XX_BOARD_PINS,
|
||||
pin_validation=None,
|
||||
usage_validation=None,
|
||||
)
|
||||
|
||||
|
||||
def _set_core_data(config):
|
||||
CORE.data[KEY_LIBRETINY] = {}
|
||||
CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] = COMPONENT_DATA
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = libretiny.BASE_SCHEMA
|
||||
|
||||
PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA
|
||||
|
||||
CONFIG_SCHEMA.prepend_extra(_set_core_data)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
return await libretiny.component_to_code(config)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("bk72xx", PIN_SCHEMA)
|
||||
async def pin_to_code(config):
|
||||
return await libretiny.gpio.component_pin_to_code(config)
|
1264
esphome/components/bk72xx/boards.py
Normal file
1264
esphome/components/bk72xx/boards.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -21,7 +21,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on(["esp32", "esp8266"]),
|
||||
cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]),
|
||||
)
|
||||
|
||||
|
||||
|
@ -39,3 +39,5 @@ async def to_code(config):
|
|||
cg.add_library("WiFi", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("DNSServer", None)
|
||||
if CORE.is_libretiny:
|
||||
cg.add_library("DNSServer", None)
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
#ifdef USE_ARDUINO
|
||||
#ifdef USE_RP2040
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#elif defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
#include <Esp.h>
|
||||
#endif
|
||||
#endif
|
||||
|
@ -45,6 +45,8 @@ static uint32_t get_free_heap() {
|
|||
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
#elif defined(USE_RP2040)
|
||||
return rp2040.getFreeHeap();
|
||||
#elif defined(USE_LIBRETINY)
|
||||
return lt_heap_get_free();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -75,7 +77,7 @@ void DebugComponent::dump_config() {
|
|||
this->free_heap_ = get_free_heap();
|
||||
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
|
||||
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2040)
|
||||
#if defined(USE_ARDUINO) && (defined(USE_ESP32) || defined(USE_ESP8266))
|
||||
const char *flash_mode;
|
||||
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
|
||||
case FM_QIO:
|
||||
|
@ -107,7 +109,7 @@ void DebugComponent::dump_config() {
|
|||
device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT
|
||||
"kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT
|
||||
device_info += flash_mode;
|
||||
#endif // USE_ARDUINO
|
||||
#endif // USE_ARDUINO && (USE_ESP32 || USE_ESP8266)
|
||||
|
||||
#ifdef USE_ESP32
|
||||
esp_chip_info_t info;
|
||||
|
@ -340,6 +342,27 @@ void DebugComponent::dump_config() {
|
|||
device_info += "CPU Frequency: " + to_string(rp2040.f_cpu());
|
||||
#endif // USE_RP2040
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version());
|
||||
ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz());
|
||||
ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id());
|
||||
ESP_LOGD(TAG, "Board: %s", lt_get_board_code());
|
||||
ESP_LOGD(TAG, "Flash: %u KiB / RAM: %u KiB", lt_flash_get_size() / 1024, lt_ram_get_size() / 1024);
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", lt_get_reboot_reason_name(lt_get_reboot_reason()));
|
||||
|
||||
device_info += "|Version: ";
|
||||
device_info += LT_BANNER_STR + 10;
|
||||
device_info += "|Reset Reason: ";
|
||||
device_info += lt_get_reboot_reason_name(lt_get_reboot_reason());
|
||||
device_info += "|Chip Name: ";
|
||||
device_info += lt_cpu_get_model_name();
|
||||
device_info += "|Chip ID: 0x" + format_hex(lt_cpu_get_mac_id());
|
||||
device_info += "|Flash: " + to_string(lt_flash_get_size() / 1024) + " KiB";
|
||||
device_info += "|RAM: " + to_string(lt_ram_get_size() / 1024) + " KiB";
|
||||
|
||||
reset_reason = lt_get_reboot_reason_name(lt_get_reboot_reason());
|
||||
#endif // USE_LIBRETINY
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->device_info_ != nullptr) {
|
||||
if (device_info.length() > 255)
|
||||
|
@ -384,6 +407,8 @@ void DebugComponent::update() {
|
|||
this->block_sensor_->publish_state(ESP.getMaxFreeBlockSize());
|
||||
#elif defined(USE_ESP32)
|
||||
this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL));
|
||||
#elif defined(USE_LIBRETINY)
|
||||
this->block_sensor_->publish_state(lt_heap_get_max_alloc());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
|||
from dataclasses import dataclass
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ANALOG,
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_INVERTED,
|
||||
|
@ -140,7 +141,6 @@ def validate_supports(value):
|
|||
return value
|
||||
|
||||
|
||||
CONF_ANALOG = "analog"
|
||||
ESP8266_PIN_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP8266GPIOPin),
|
||||
|
|
|
@ -42,23 +42,26 @@ pin_with_input_and_output_support = cv.All(
|
|||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): _bus_declare_type,
|
||||
cv.Optional(CONF_SDA, default="SDA"): pin_with_input_and_output_support,
|
||||
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
||||
cv.only_with_esp_idf, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_SCL, default="SCL"): pin_with_input_and_output_support,
|
||||
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
||||
cv.only_with_esp_idf, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All(
|
||||
cv.frequency, cv.Range(min=0, min_included=False)
|
||||
),
|
||||
cv.Optional(CONF_SCAN, default=True): cv.boolean,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): _bus_declare_type,
|
||||
cv.Optional(CONF_SDA, default="SDA"): pin_with_input_and_output_support,
|
||||
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
||||
cv.only_with_esp_idf, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_SCL, default="SCL"): pin_with_input_and_output_support,
|
||||
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
||||
cv.only_with_esp_idf, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All(
|
||||
cv.frequency, cv.Range(min=0, min_included=False)
|
||||
),
|
||||
cv.Optional(CONF_SCAN, default=True): cv.boolean,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on(["esp32", "esp8266", "rp2040"]),
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(1.0)
|
||||
|
|
|
@ -29,6 +29,8 @@ std::string build_json(const json_build_t &f) {
|
|||
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
|
||||
#elif defined(USE_RP2040)
|
||||
const size_t free_heap = rp2040.getFreeHeap();
|
||||
#elif defined(USE_LIBRETINY)
|
||||
const size_t free_heap = lt_heap_get_free();
|
||||
#endif
|
||||
|
||||
size_t request_size = std::min(free_heap, (size_t) 512);
|
||||
|
@ -71,6 +73,8 @@ void parse_json(const std::string &data, const json_parse_t &f) {
|
|||
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
|
||||
#elif defined(USE_RP2040)
|
||||
const size_t free_heap = rp2040.getFreeHeap();
|
||||
#elif defined(USE_LIBRETINY)
|
||||
const size_t free_heap = lt_heap_get_free();
|
||||
#endif
|
||||
bool pass = false;
|
||||
size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
|
||||
|
|
336
esphome/components/libretiny/__init__.py
Normal file
336
esphome/components/libretiny/__init__.py
Normal file
|
@ -0,0 +1,336 @@
|
|||
import json
|
||||
import logging
|
||||
from os.path import dirname, isfile, join
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
CONF_COMPONENT_ID,
|
||||
CONF_DEBUG,
|
||||
CONF_FAMILY,
|
||||
CONF_FRAMEWORK,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_OPTIONS,
|
||||
CONF_PROJECT,
|
||||
CONF_SOURCE,
|
||||
CONF_VERSION,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
from . import gpio # noqa
|
||||
from .const import (
|
||||
CONF_GPIO_RECOVER,
|
||||
CONF_LOGLEVEL,
|
||||
CONF_SDK_SILENT,
|
||||
CONF_UART_PORT,
|
||||
FAMILIES,
|
||||
FAMILY_COMPONENT,
|
||||
FAMILY_FRIENDLY,
|
||||
KEY_BOARD,
|
||||
KEY_COMPONENT,
|
||||
KEY_COMPONENT_DATA,
|
||||
KEY_FAMILY,
|
||||
KEY_LIBRETINY,
|
||||
LT_DEBUG_MODULES,
|
||||
LT_LOGLEVELS,
|
||||
LibreTinyComponent,
|
||||
LTComponent,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = []
|
||||
|
||||
|
||||
def _detect_variant(value):
|
||||
if KEY_LIBRETINY not in CORE.data:
|
||||
raise cv.Invalid("Family component didn't populate core data properly!")
|
||||
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
|
||||
board = value[CONF_BOARD]
|
||||
# read board-default family if not specified
|
||||
if CONF_FAMILY not in value:
|
||||
if board not in component.boards:
|
||||
raise cv.Invalid(
|
||||
"This board is unknown, please set the family manually. "
|
||||
"Also, make sure the chosen chip component is correct.",
|
||||
path=[CONF_BOARD],
|
||||
)
|
||||
value = value.copy()
|
||||
value[CONF_FAMILY] = component.boards[board][KEY_FAMILY]
|
||||
# read component name matching this family
|
||||
value[CONF_COMPONENT_ID] = FAMILY_COMPONENT[value[CONF_FAMILY]]
|
||||
# make sure the chosen component matches the family
|
||||
if value[CONF_COMPONENT_ID] != component.name:
|
||||
raise cv.Invalid(
|
||||
f"The chosen family doesn't belong to '{component.name}' component. The correct component is '{value[CONF_COMPONENT_ID]}'",
|
||||
path=[CONF_FAMILY],
|
||||
)
|
||||
# warn anyway if the board wasn't found
|
||||
if board not in component.boards:
|
||||
_LOGGER.warning(
|
||||
"This board is unknown. Make sure the chosen chip component is correct.",
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def _update_core_data(config):
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = config[CONF_COMPONENT_ID]
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||
config[CONF_FRAMEWORK][CONF_VERSION]
|
||||
)
|
||||
CORE.data[KEY_LIBRETINY][KEY_BOARD] = config[CONF_BOARD]
|
||||
CORE.data[KEY_LIBRETINY][KEY_COMPONENT] = config[CONF_COMPONENT_ID]
|
||||
CORE.data[KEY_LIBRETINY][KEY_FAMILY] = config[CONF_FAMILY]
|
||||
return config
|
||||
|
||||
|
||||
def get_libretiny_component(core_obj=None):
|
||||
return (core_obj or CORE).data[KEY_LIBRETINY][KEY_COMPONENT]
|
||||
|
||||
|
||||
def get_libretiny_family(core_obj=None):
|
||||
return (core_obj or CORE).data[KEY_LIBRETINY][KEY_FAMILY]
|
||||
|
||||
|
||||
def only_on_family(*, supported=None, unsupported=None):
|
||||
"""Config validator for features only available on some LibreTiny families."""
|
||||
if supported is not None and not isinstance(supported, list):
|
||||
supported = [supported]
|
||||
if unsupported is not None and not isinstance(unsupported, list):
|
||||
unsupported = [unsupported]
|
||||
|
||||
def validator_(obj):
|
||||
family = get_libretiny_family()
|
||||
if supported is not None and family not in supported:
|
||||
raise cv.Invalid(
|
||||
f"This feature is only available on {', '.join(supported)}"
|
||||
)
|
||||
if unsupported is not None and family in unsupported:
|
||||
raise cv.Invalid(
|
||||
f"This feature is not available on {', '.join(unsupported)}"
|
||||
)
|
||||
return obj
|
||||
|
||||
return validator_
|
||||
|
||||
|
||||
def get_download_types(storage_json=None):
|
||||
types = [
|
||||
{
|
||||
"title": "UF2 package (recommended)",
|
||||
"description": "For flashing via web_server OTA or with ltchiptool (UART)",
|
||||
"file": "firmware.uf2",
|
||||
"download": f"{storage_json.name}.uf2",
|
||||
},
|
||||
]
|
||||
|
||||
build_dir = dirname(storage_json.firmware_bin_path)
|
||||
outputs = join(build_dir, "firmware.json")
|
||||
if not isfile(outputs):
|
||||
return types
|
||||
with open(outputs, encoding="utf-8") as f:
|
||||
outputs = json.load(f)
|
||||
for output in outputs:
|
||||
if not output["public"]:
|
||||
continue
|
||||
suffix = output["filename"].partition(".")[2]
|
||||
suffix = f"-{suffix}" if "." in suffix else f".{suffix}"
|
||||
types.append(
|
||||
{
|
||||
"title": output["title"],
|
||||
"description": output["description"],
|
||||
"file": output["filename"],
|
||||
"download": storage_json.name + suffix,
|
||||
}
|
||||
)
|
||||
return types
|
||||
|
||||
|
||||
def _notify_old_style(config):
|
||||
if config:
|
||||
raise cv.Invalid(
|
||||
"The LibreTiny component is now split between supported chip families.\n"
|
||||
"Migrate your config file to include a chip-based configuration, "
|
||||
"instead of the 'libretiny:' block.\n"
|
||||
"For example 'bk72xx:' or 'rtl87xx:'."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
# NOTE: Keep this in mind when updating the recommended version:
|
||||
# * For all constants below, update platformio.ini (in this repo)
|
||||
ARDUINO_VERSIONS = {
|
||||
"dev": (cv.Version(0, 0, 0), "https://github.com/kuba2k2/libretiny.git"),
|
||||
"latest": (cv.Version(0, 0, 0), None),
|
||||
"recommended": (cv.Version(1, 3, 0), None),
|
||||
}
|
||||
|
||||
|
||||
def _check_framework_version(value):
|
||||
value = value.copy()
|
||||
|
||||
if value[CONF_VERSION] in ARDUINO_VERSIONS:
|
||||
if CONF_SOURCE in value:
|
||||
raise cv.Invalid(
|
||||
"Framework version needs to be explicitly specified when custom source is used."
|
||||
)
|
||||
|
||||
version, source = ARDUINO_VERSIONS[value[CONF_VERSION]]
|
||||
else:
|
||||
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
|
||||
source = value.get(CONF_SOURCE, None)
|
||||
|
||||
value[CONF_VERSION] = str(version)
|
||||
value[CONF_SOURCE] = source
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _check_debug_order(value):
|
||||
debug = value[CONF_DEBUG]
|
||||
if "NONE" in debug and "NONE" in debug[1:]:
|
||||
raise cv.Invalid(
|
||||
"'none' has to be specified before other modules, and only once",
|
||||
path=[CONF_DEBUG],
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.string_strict,
|
||||
cv.Optional(CONF_LOGLEVEL, default="warn"): (
|
||||
cv.one_of(*LT_LOGLEVELS, upper=True)
|
||||
),
|
||||
cv.Optional(CONF_DEBUG, default=[]): cv.ensure_list(
|
||||
cv.one_of("NONE", *LT_DEBUG_MODULES, upper=True)
|
||||
),
|
||||
cv.Optional(CONF_SDK_SILENT, default="all"): (
|
||||
cv.one_of("all", "auto", "none", lower=True)
|
||||
),
|
||||
cv.Optional(CONF_UART_PORT, default=None): cv.one_of(0, 1, 2, int=True),
|
||||
cv.Optional(CONF_GPIO_RECOVER, default=True): cv.boolean,
|
||||
cv.Optional(CONF_OPTIONS, default={}): {
|
||||
cv.string_strict: cv.string,
|
||||
},
|
||||
}
|
||||
),
|
||||
_check_framework_version,
|
||||
_check_debug_order,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(_notify_old_style)
|
||||
|
||||
BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LTComponent),
|
||||
cv.Required(CONF_BOARD): cv.string_strict,
|
||||
cv.Optional(CONF_FAMILY): cv.one_of(*FAMILIES, upper=True),
|
||||
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
|
||||
},
|
||||
)
|
||||
|
||||
BASE_SCHEMA.add_extra(_detect_variant)
|
||||
BASE_SCHEMA.add_extra(_update_core_data)
|
||||
|
||||
|
||||
# pylint: disable=use-dict-literal
|
||||
async def component_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
# setup board config
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
cg.add_build_flag("-DUSE_LIBRETINY")
|
||||
cg.add_build_flag(f"-DUSE_{config[CONF_COMPONENT_ID]}")
|
||||
cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]])
|
||||
|
||||
# force using arduino framework
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
|
||||
# disable library compatibility checks
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
# include <Arduino.h> in every file
|
||||
cg.add_platformio_option("build_src_flags", "-include Arduino.h")
|
||||
# dummy version code
|
||||
cg.add_define("USE_ARDUINO_VERSION_CODE", cg.RawExpression("VERSION_CODE(0, 0, 0)"))
|
||||
# decrease web server stack size (16k words -> 4k words)
|
||||
cg.add_build_flag("-DCONFIG_ASYNC_TCP_STACK_SIZE=4096")
|
||||
|
||||
# build framework version
|
||||
# if platform version is a valid version constraint, prefix the default package
|
||||
framework = config[CONF_FRAMEWORK]
|
||||
cv.platformio_version_constraint(framework[CONF_VERSION])
|
||||
if str(framework[CONF_VERSION]) != "0.0.0":
|
||||
cg.add_platformio_option("platform", f"libretiny @ {framework[CONF_VERSION]}")
|
||||
elif framework[CONF_SOURCE]:
|
||||
cg.add_platformio_option("platform", framework[CONF_SOURCE])
|
||||
else:
|
||||
cg.add_platformio_option("platform", "libretiny")
|
||||
|
||||
# apply LibreTiny options from framework: block
|
||||
# setup LT logger to work nicely with ESPHome logger
|
||||
lt_options = dict(
|
||||
LT_LOGLEVEL="LT_LEVEL_" + framework[CONF_LOGLEVEL],
|
||||
LT_LOGGER_CALLER=0,
|
||||
LT_LOGGER_TASK=0,
|
||||
LT_LOGGER_COLOR=1,
|
||||
LT_USE_TIME=1,
|
||||
)
|
||||
# enable/disable per-module debugging
|
||||
for module in framework[CONF_DEBUG]:
|
||||
if module == "NONE":
|
||||
# disable all modules
|
||||
for module in LT_DEBUG_MODULES:
|
||||
lt_options[f"LT_DEBUG_{module}"] = 0
|
||||
else:
|
||||
# enable one module
|
||||
lt_options[f"LT_DEBUG_{module}"] = 1
|
||||
# set SDK silencing mode
|
||||
if framework[CONF_SDK_SILENT] == "all":
|
||||
lt_options["LT_UART_SILENT_ENABLED"] = 1
|
||||
lt_options["LT_UART_SILENT_ALL"] = 1
|
||||
elif framework[CONF_SDK_SILENT] == "auto":
|
||||
lt_options["LT_UART_SILENT_ENABLED"] = 1
|
||||
lt_options["LT_UART_SILENT_ALL"] = 0
|
||||
else:
|
||||
lt_options["LT_UART_SILENT_ENABLED"] = 0
|
||||
lt_options["LT_UART_SILENT_ALL"] = 0
|
||||
# set default UART port
|
||||
if framework[CONF_UART_PORT] is not None:
|
||||
lt_options["LT_UART_DEFAULT_PORT"] = framework[CONF_UART_PORT]
|
||||
# add custom options
|
||||
lt_options.update(framework[CONF_OPTIONS])
|
||||
|
||||
# apply ESPHome options from framework: block
|
||||
cg.add_define("LT_GPIO_RECOVER", int(framework[CONF_GPIO_RECOVER]))
|
||||
|
||||
# build PlatformIO compiler flags
|
||||
for name, value in sorted(lt_options.items()):
|
||||
cg.add_build_flag(f"-D{name}={value}")
|
||||
|
||||
# custom output firmware name and version
|
||||
if CONF_PROJECT in config:
|
||||
cg.add_platformio_option(
|
||||
"custom_fw_name", "esphome." + config[CONF_PROJECT][CONF_NAME]
|
||||
)
|
||||
cg.add_platformio_option(
|
||||
"custom_fw_version", config[CONF_PROJECT][CONF_VERSION]
|
||||
)
|
||||
else:
|
||||
cg.add_platformio_option("custom_fw_name", "esphome")
|
||||
cg.add_platformio_option("custom_fw_version", __version__)
|
||||
|
||||
await cg.register_component(var, config)
|
90
esphome/components/libretiny/const.py
Normal file
90
esphome/components/libretiny/const.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
import esphome.codegen as cg
|
||||
|
||||
|
||||
@dataclass
|
||||
class LibreTinyComponent:
|
||||
name: str
|
||||
boards: dict[str, dict[str, str]]
|
||||
board_pins: dict[str, dict[str, int]]
|
||||
pin_validation: Callable[[int], int]
|
||||
usage_validation: Callable[[dict], dict]
|
||||
|
||||
|
||||
CONF_LIBRETINY = "libretiny"
|
||||
CONF_LOGLEVEL = "loglevel"
|
||||
CONF_SDK_SILENT = "sdk_silent"
|
||||
CONF_GPIO_RECOVER = "gpio_recover"
|
||||
CONF_UART_PORT = "uart_port"
|
||||
|
||||
LT_LOGLEVELS = [
|
||||
"VERBOSE",
|
||||
"TRACE",
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"WARN",
|
||||
"ERROR",
|
||||
"FATAL",
|
||||
"NONE",
|
||||
]
|
||||
|
||||
LT_DEBUG_MODULES = [
|
||||
"WIFI",
|
||||
"CLIENT",
|
||||
"SERVER",
|
||||
"SSL",
|
||||
"OTA",
|
||||
"FDB",
|
||||
"MDNS",
|
||||
"LWIP",
|
||||
"LWIP_ASSERT",
|
||||
]
|
||||
|
||||
KEY_LIBRETINY = "libretiny"
|
||||
KEY_BOARD = "board"
|
||||
KEY_COMPONENT = "component"
|
||||
KEY_COMPONENT_DATA = "component_data"
|
||||
KEY_FAMILY = "family"
|
||||
|
||||
# COMPONENTS - auto-generated! Do not modify this block.
|
||||
COMPONENT_BK72XX = "bk72xx"
|
||||
COMPONENT_RTL87XX = "rtl87xx"
|
||||
# COMPONENTS - end
|
||||
|
||||
# FAMILIES - auto-generated! Do not modify this block.
|
||||
FAMILY_BK7231N = "BK7231N"
|
||||
FAMILY_BK7231Q = "BK7231Q"
|
||||
FAMILY_BK7231T = "BK7231T"
|
||||
FAMILY_BK7251 = "BK7251"
|
||||
FAMILY_RTL8710B = "RTL8710B"
|
||||
FAMILY_RTL8720C = "RTL8720C"
|
||||
FAMILIES = [
|
||||
FAMILY_BK7231N,
|
||||
FAMILY_BK7231Q,
|
||||
FAMILY_BK7231T,
|
||||
FAMILY_BK7251,
|
||||
FAMILY_RTL8710B,
|
||||
FAMILY_RTL8720C,
|
||||
]
|
||||
FAMILY_FRIENDLY = {
|
||||
FAMILY_BK7231N: "BK7231N",
|
||||
FAMILY_BK7231Q: "BK7231Q",
|
||||
FAMILY_BK7231T: "BK7231T",
|
||||
FAMILY_BK7251: "BK7251",
|
||||
FAMILY_RTL8710B: "RTL8710B",
|
||||
FAMILY_RTL8720C: "RTL8720C",
|
||||
}
|
||||
FAMILY_COMPONENT = {
|
||||
FAMILY_BK7231N: COMPONENT_BK72XX,
|
||||
FAMILY_BK7231Q: COMPONENT_BK72XX,
|
||||
FAMILY_BK7231T: COMPONENT_BK72XX,
|
||||
FAMILY_BK7251: COMPONENT_BK72XX,
|
||||
FAMILY_RTL8710B: COMPONENT_RTL87XX,
|
||||
FAMILY_RTL8720C: COMPONENT_RTL87XX,
|
||||
}
|
||||
# FAMILIES - end
|
||||
|
||||
libretiny_ns = cg.esphome_ns.namespace("libretiny")
|
||||
LTComponent = libretiny_ns.class_("LTComponent", cg.PollingComponent)
|
40
esphome/components/libretiny/core.cpp
Normal file
40
esphome/components/libretiny/core.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "core.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "preferences.h"
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void IRAM_ATTR HOT yield() { ::yield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); }
|
||||
|
||||
void arch_init() {
|
||||
libretiny::setup_preferences();
|
||||
lt_wdt_enable(10000L);
|
||||
#if LT_GPIO_RECOVER
|
||||
lt_gpio_recover();
|
||||
#endif
|
||||
}
|
||||
|
||||
void arch_restart() {
|
||||
lt_reboot();
|
||||
while (1) {
|
||||
}
|
||||
}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() { lt_wdt_feed(); }
|
||||
uint32_t arch_get_cpu_cycle_count() { return lt_cpu_get_cycle_count(); }
|
||||
uint32_t arch_get_cpu_freq_hz() { return lt_cpu_get_freq(); }
|
||||
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
11
esphome/components/libretiny/core.h
Normal file
11
esphome/components/libretiny/core.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny {} // namespace libretiny
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
329
esphome/components/libretiny/generate_components.py
Normal file
329
esphome/components/libretiny/generate_components.py
Normal file
|
@ -0,0 +1,329 @@
|
|||
# Copyright (c) Kuba Szczodrzyński 2023-06-01.
|
||||
|
||||
# pylint: skip-file
|
||||
# flake8: noqa
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from black import FileMode, format_str
|
||||
from ltchiptool import Board, Family
|
||||
from ltchiptool.util.lvm import LVM
|
||||
|
||||
BASE_CODE_INIT = """
|
||||
# This file was auto-generated by libretiny/generate_components.py
|
||||
# Do not modify its contents.
|
||||
# For custom pin validators, put validate_pin() or validate_usage()
|
||||
# in gpio.py file in this directory.
|
||||
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
# in schema.py file in this directory.
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import libretiny
|
||||
from esphome.components.libretiny.const import (
|
||||
COMPONENT_{COMPONENT},
|
||||
KEY_COMPONENT_DATA,
|
||||
KEY_LIBRETINY,
|
||||
LibreTinyComponent,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
{IMPORTS}
|
||||
|
||||
CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = ["libretiny"]
|
||||
|
||||
COMPONENT_DATA = LibreTinyComponent(
|
||||
name=COMPONENT_{COMPONENT},
|
||||
boards={COMPONENT}_BOARDS,
|
||||
board_pins={COMPONENT}_BOARD_PINS,
|
||||
pin_validation={PIN_VALIDATION},
|
||||
usage_validation={USAGE_VALIDATION},
|
||||
)
|
||||
|
||||
|
||||
def _set_core_data(config):
|
||||
CORE.data[KEY_LIBRETINY] = {}
|
||||
CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] = COMPONENT_DATA
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = {SCHEMA}
|
||||
|
||||
PIN_SCHEMA = {PIN_SCHEMA}
|
||||
|
||||
CONFIG_SCHEMA.prepend_extra(_set_core_data)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
return await libretiny.component_to_code(config)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("{COMPONENT_LOWER}", PIN_SCHEMA)
|
||||
async def pin_to_code(config):
|
||||
return await libretiny.gpio.component_pin_to_code(config)
|
||||
"""
|
||||
|
||||
BASE_CODE_BOARDS = """
|
||||
# This file was auto-generated by libretiny/generate_components.py
|
||||
# Do not modify its contents.
|
||||
|
||||
from esphome.components.libretiny.const import {FAMILIES}
|
||||
|
||||
{COMPONENT}_BOARDS = {BOARDS_JSON}
|
||||
|
||||
{COMPONENT}_BOARD_PINS = {PINS_JSON}
|
||||
|
||||
BOARDS = {COMPONENT}_BOARDS
|
||||
"""
|
||||
|
||||
# variable names in component extension code
|
||||
VAR_SCHEMA = "COMPONENT_SCHEMA"
|
||||
VAR_PIN_SCHEMA = "COMPONENT_PIN_SCHEMA"
|
||||
VAR_GPIO_PIN = "validate_pin"
|
||||
VAR_GPIO_USAGE = "validate_usage"
|
||||
|
||||
# lines for code snippets
|
||||
SCHEMA_BASE = "libretiny.BASE_SCHEMA"
|
||||
SCHEMA_EXTRA = f"libretiny.BASE_SCHEMA.extend({VAR_SCHEMA})"
|
||||
PIN_SCHEMA_BASE = "libretiny.gpio.BASE_PIN_SCHEMA"
|
||||
PIN_SCHEMA_EXTRA = f"libretiny.BASE_PIN_SCHEMA.extend({VAR_PIN_SCHEMA})"
|
||||
|
||||
# supported root components
|
||||
COMPONENT_MAP = {
|
||||
"rtl87xx": "realtek-amb",
|
||||
"bk72xx": "beken-72xx",
|
||||
}
|
||||
|
||||
|
||||
def subst(code: str, key: str, value: str) -> str:
|
||||
return code.replace(f"{{{key}}}", value)
|
||||
|
||||
|
||||
def subst_all(code: str, value: str) -> str:
|
||||
return re.sub(r"{.+?}", value, code)
|
||||
|
||||
|
||||
def subst_many(code: str, *templates: tuple[str, str]) -> str:
|
||||
while True:
|
||||
prev_code = code
|
||||
for key, value in templates:
|
||||
code = subst(code, key, value)
|
||||
if code == prev_code:
|
||||
break
|
||||
return code
|
||||
|
||||
|
||||
def check_base_code(code: str) -> None:
|
||||
code = subst_all(code, "DUMMY")
|
||||
formatted = format_str(code, mode=FileMode())
|
||||
if code.strip() != formatted.strip():
|
||||
print(formatted)
|
||||
raise RuntimeError("Base code is not formatted properly")
|
||||
|
||||
|
||||
def write_component_code(
|
||||
component_dir: Path,
|
||||
component: str,
|
||||
) -> None:
|
||||
code = BASE_CODE_INIT
|
||||
gpio_py = component_dir.joinpath("gpio.py")
|
||||
schema_py = component_dir.joinpath("schema.py")
|
||||
init_py = component_dir.joinpath("__init__.py")
|
||||
|
||||
# gather all imports
|
||||
imports = {
|
||||
"gpio": set(),
|
||||
"schema": set(),
|
||||
"boards": {"{COMPONENT}_BOARDS", "{COMPONENT}_BOARD_PINS"},
|
||||
}
|
||||
# substitution values
|
||||
values = dict(
|
||||
COMPONENT=component.upper(),
|
||||
COMPONENT_LOWER=component.lower(),
|
||||
SCHEMA=SCHEMA_BASE,
|
||||
PIN_SCHEMA=PIN_SCHEMA_BASE,
|
||||
PIN_VALIDATION="None",
|
||||
USAGE_VALIDATION="None",
|
||||
)
|
||||
|
||||
# parse gpio.py file to find custom validators
|
||||
if gpio_py.is_file():
|
||||
gpio_code = gpio_py.read_text()
|
||||
if VAR_GPIO_PIN in gpio_code:
|
||||
values["PIN_VALIDATION"] = VAR_GPIO_PIN
|
||||
imports["gpio"].add(VAR_GPIO_PIN)
|
||||
|
||||
# parse schema.py file to find schema extension
|
||||
if schema_py.is_file():
|
||||
schema_code = schema_py.read_text()
|
||||
if VAR_SCHEMA in schema_code:
|
||||
values["SCHEMA"] = SCHEMA_EXTRA
|
||||
imports["schema"].add(VAR_SCHEMA)
|
||||
if VAR_PIN_SCHEMA in schema_code:
|
||||
values["PIN_SCHEMA"] = PIN_SCHEMA_EXTRA
|
||||
imports["schema"].add(VAR_PIN_SCHEMA)
|
||||
|
||||
# add import lines if needed
|
||||
import_lines = "\n".join(
|
||||
f"from .{m} import {', '.join(sorted(v))}" for m, v in imports.items() if v
|
||||
)
|
||||
code = subst_many(
|
||||
code,
|
||||
("IMPORTS", import_lines),
|
||||
*values.items(),
|
||||
)
|
||||
# format with black
|
||||
code = format_str(code, mode=FileMode())
|
||||
# write back to file
|
||||
init_py.write_text(code)
|
||||
|
||||
|
||||
def write_component_boards(
|
||||
component_dir: Path,
|
||||
component: str,
|
||||
boards: list[Board],
|
||||
) -> list[Family]:
|
||||
code = BASE_CODE_BOARDS
|
||||
variants_dir = Path(LVM.path(), "boards", "variants")
|
||||
boards_py = component_dir.joinpath("boards.py")
|
||||
pin_regex = r"#define PIN_(\w+)\s+(\d+)"
|
||||
pin_number_regex = r"0*(\d+)$"
|
||||
|
||||
# families to import
|
||||
families = set()
|
||||
# found root families
|
||||
root_families = []
|
||||
# substitution values
|
||||
values = dict(
|
||||
COMPONENT=component.upper(),
|
||||
)
|
||||
# resulting JSON objects
|
||||
boards_json = {}
|
||||
pins_json = {}
|
||||
|
||||
# go through all boards found for this root family
|
||||
for board in boards:
|
||||
family = "FAMILY_" + board.family.short_name
|
||||
boards_json[board.name] = {
|
||||
"name": board.title,
|
||||
"family": family,
|
||||
}
|
||||
families.add(family)
|
||||
if board.family not in root_families:
|
||||
root_families.append(board.family)
|
||||
|
||||
board_h = variants_dir.joinpath(f"{board.name}.h")
|
||||
board_code = board_h.read_text()
|
||||
board_pins = {}
|
||||
for match in re.finditer(pin_regex, board_code):
|
||||
pin_name = match[1]
|
||||
pin_value = match[2]
|
||||
board_pins[pin_name] = int(pin_value)
|
||||
# trim leading zeroes in GPIO numbers
|
||||
pin_name = re.sub(pin_number_regex, r"\1", pin_name)
|
||||
board_pins[pin_name] = int(pin_value)
|
||||
pins_json[board.name] = board_pins
|
||||
|
||||
# make the JSONs format as non-inline
|
||||
boards_json = json.dumps(boards_json).replace("}", ",}")
|
||||
pins_json = json.dumps(pins_json).replace("}", ",}")
|
||||
# remove quotes from family constants
|
||||
for family in families:
|
||||
boards_json = boards_json.replace(f'"{family}"', family)
|
||||
code = subst_many(
|
||||
code,
|
||||
("FAMILIES", ", ".join(sorted(families))),
|
||||
("BOARDS_JSON", boards_json),
|
||||
("PINS_JSON", pins_json),
|
||||
*values.items(),
|
||||
)
|
||||
# format with black
|
||||
code = format_str(code, mode=FileMode())
|
||||
# write back to file
|
||||
boards_py.write_text(code)
|
||||
return root_families
|
||||
|
||||
|
||||
def write_const(
|
||||
components_dir: Path,
|
||||
components: set[str],
|
||||
families: dict[str, str],
|
||||
) -> None:
|
||||
const_py = components_dir.joinpath("libretiny").joinpath("const.py")
|
||||
if not const_py.is_file():
|
||||
raise FileNotFoundError(const_py)
|
||||
code = const_py.read_text()
|
||||
components = sorted(components)
|
||||
v2f = families
|
||||
families = sorted(families)
|
||||
|
||||
# regex for finding the component list block
|
||||
comp_regex = r"(# COMPONENTS.+?\n)(.*?)(\n# COMPONENTS)"
|
||||
# build component constants
|
||||
comp_str = "\n".join(f'COMPONENT_{f} = "{f.lower()}"' for f in components)
|
||||
# replace the 2nd regex group only
|
||||
repl = lambda m: m.group(1) + comp_str + m.group(3)
|
||||
code = re.sub(comp_regex, repl, code, flags=re.DOTALL | re.MULTILINE)
|
||||
|
||||
# regex for finding the family list block
|
||||
fam_regex = r"(# FAMILIES.+?\n)(.*?)(\n# FAMILIES)"
|
||||
# build family constants
|
||||
fam_defs = "\n".join(f'FAMILY_{v} = "{v}"' for v in families)
|
||||
fam_list = ", ".join(f"FAMILY_{v}" for v in families)
|
||||
fam_friendly = ", ".join(f'FAMILY_{v}: "{v}"' for v in families)
|
||||
fam_component = ", ".join(f"FAMILY_{v}: COMPONENT_{v2f[v]}" for v in families)
|
||||
fam_lines = [
|
||||
fam_defs,
|
||||
"FAMILIES = [",
|
||||
fam_list,
|
||||
",]",
|
||||
"FAMILY_FRIENDLY = {",
|
||||
fam_friendly,
|
||||
",}",
|
||||
"FAMILY_COMPONENT = {",
|
||||
fam_component,
|
||||
",}",
|
||||
]
|
||||
var_str = "\n".join(fam_lines)
|
||||
# replace the 2nd regex group only
|
||||
repl = lambda m: m.group(1) + var_str + m.group(3)
|
||||
code = re.sub(fam_regex, repl, code, flags=re.DOTALL | re.MULTILINE)
|
||||
|
||||
# format with black
|
||||
code = format_str(code, mode=FileMode())
|
||||
# write back to file
|
||||
const_py.write_text(code)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# safety check if code is properly formatted
|
||||
check_base_code(BASE_CODE_INIT)
|
||||
# list all boards from ltchiptool
|
||||
components_dir = Path(__file__).parent.parent
|
||||
boards = [Board(b) for b in Board.get_list()]
|
||||
# keep track of all supported root- and chip-families
|
||||
components = set()
|
||||
families = {}
|
||||
# loop through supported components
|
||||
for component, family_name in COMPONENT_MAP.items():
|
||||
family = Family.get(name=family_name)
|
||||
# make family component directory
|
||||
component_dir = components_dir.joinpath(component)
|
||||
component_dir.mkdir(exist_ok=True)
|
||||
# filter boards list
|
||||
family_boards = [b for b in boards if family in b.family.inheritance]
|
||||
# write __init__.py
|
||||
write_component_code(component_dir, component)
|
||||
# write boards.py
|
||||
component_families = write_component_boards(
|
||||
component_dir, component, family_boards
|
||||
)
|
||||
# store current root component name
|
||||
components.add(component.upper())
|
||||
# add all chip families
|
||||
for family in component_families:
|
||||
families[family.short_name] = component.upper()
|
||||
# update libretiny/const.py
|
||||
write_const(components_dir, components, families)
|
216
esphome/components/libretiny/gpio.py
Normal file
216
esphome/components/libretiny/gpio.py
Normal file
|
@ -0,0 +1,216 @@
|
|||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_ANALOG,
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_INVERTED,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OPEN_DRAIN,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
from .const import (
|
||||
KEY_BOARD,
|
||||
KEY_COMPONENT_DATA,
|
||||
KEY_LIBRETINY,
|
||||
LibreTinyComponent,
|
||||
libretiny_ns,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ArduinoInternalGPIOPin = libretiny_ns.class_(
|
||||
"ArduinoInternalGPIOPin", cg.InternalGPIOPin
|
||||
)
|
||||
|
||||
|
||||
def _is_name_deprecated(value):
|
||||
return value[0] in "DA" and value[1:].isnumeric()
|
||||
|
||||
|
||||
def _lookup_board_pins(board):
|
||||
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
|
||||
board_pins = component.board_pins.get(board, {})
|
||||
# Resolve aliased board pins (shorthand when two boards have the same pin configuration)
|
||||
while isinstance(board_pins, str):
|
||||
board_pins = board_pins[board_pins]
|
||||
return board_pins
|
||||
|
||||
|
||||
def _lookup_pin(value):
|
||||
board: str = CORE.data[KEY_LIBRETINY][KEY_BOARD]
|
||||
board_pins = _lookup_board_pins(board)
|
||||
|
||||
# check numeric pin values
|
||||
if isinstance(value, int):
|
||||
if value in board_pins.values() or not board_pins:
|
||||
# accept if pin number present in board pins
|
||||
# if board is not found, just accept all numeric values
|
||||
return value
|
||||
raise cv.Invalid(f"Pin number '{value}' is not usable for board {board}.")
|
||||
|
||||
# check textual pin names
|
||||
if isinstance(value, str):
|
||||
if not board_pins:
|
||||
# can't remap without known pin name
|
||||
raise cv.Invalid(
|
||||
f"Board {board} wasn't found. "
|
||||
f"Use 'GPIO#' (numeric value) instead of '{value}'."
|
||||
)
|
||||
|
||||
if value in board_pins:
|
||||
# pin name found, remap to numeric value
|
||||
if _is_name_deprecated(value):
|
||||
number = board_pins[value]
|
||||
# find all alternative pin names (except the deprecated)
|
||||
names = (
|
||||
k
|
||||
for k, v in board_pins.items()
|
||||
if v == number and not _is_name_deprecated(k)
|
||||
)
|
||||
# sort by shortest
|
||||
# favor P# or PA# names
|
||||
names = sorted(
|
||||
names,
|
||||
key=lambda x: len(x) - 99 if x[0] == "P" else len(x),
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"Using D# and A# pin numbering is deprecated. "
|
||||
"Please replace '%s' with one of: %s",
|
||||
value,
|
||||
", ".join(names),
|
||||
)
|
||||
return board_pins[value]
|
||||
|
||||
# pin name not found and not numeric
|
||||
raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.")
|
||||
|
||||
# unknown type of the value
|
||||
raise cv.Invalid(f"Unrecognized pin value '{value}'.")
|
||||
|
||||
|
||||
def _translate_pin(value):
|
||||
if isinstance(value, dict) or value is None:
|
||||
raise cv.Invalid(
|
||||
"This variable only supports pin numbers, not full pin schemas "
|
||||
"(with inverted and mode)."
|
||||
)
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
# translate GPIO* and P* to a number, if possible
|
||||
# otherwise return unchanged value (i.e. pin PA05)
|
||||
try:
|
||||
if value.startswith("GPIO"):
|
||||
value = int(value[4:])
|
||||
elif value.startswith("P"):
|
||||
value = int(value[1:])
|
||||
except ValueError:
|
||||
pass
|
||||
return value
|
||||
|
||||
|
||||
def validate_gpio_pin(value):
|
||||
value = _translate_pin(value)
|
||||
value = _lookup_pin(value)
|
||||
|
||||
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
|
||||
if component.pin_validation:
|
||||
value = component.pin_validation(value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def validate_gpio_usage(value):
|
||||
mode = value[CONF_MODE]
|
||||
is_analog = mode[CONF_ANALOG]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_open_drain = mode[CONF_OPEN_DRAIN]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
|
||||
if is_open_drain and not is_output:
|
||||
raise cv.Invalid(
|
||||
"Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
|
||||
)
|
||||
if is_analog and not is_input:
|
||||
raise cv.Invalid("Analog pins must be an input", [CONF_MODE, CONF_ANALOG])
|
||||
if is_analog:
|
||||
# expect analog pin numbers to be available as either ADC# or A#
|
||||
number = value[CONF_NUMBER]
|
||||
board: str = CORE.data[KEY_LIBRETINY][KEY_BOARD]
|
||||
board_pins = _lookup_board_pins(board)
|
||||
analog_pins = [
|
||||
v
|
||||
for k, v in board_pins.items()
|
||||
if k[0] == "A" and k[1:].isnumeric() or k[0:3] == "ADC"
|
||||
]
|
||||
if number not in analog_pins:
|
||||
raise cv.Invalid(
|
||||
f"Pin '{number}' is not an analog pin", [CONF_MODE, CONF_ANALOG]
|
||||
)
|
||||
|
||||
# (input, output, open_drain, pullup, pulldown)
|
||||
supported_modes = {
|
||||
# INPUT
|
||||
(True, False, False, False, False),
|
||||
# OUTPUT
|
||||
(False, True, False, False, False),
|
||||
# INPUT_PULLUP
|
||||
(True, False, False, True, False),
|
||||
# INPUT_PULLDOWN
|
||||
(True, False, False, False, True),
|
||||
# OUTPUT_OPEN_DRAIN
|
||||
(False, True, True, False, False),
|
||||
}
|
||||
key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown)
|
||||
if key not in supported_modes:
|
||||
raise cv.Invalid("This pin mode is not supported", [CONF_MODE])
|
||||
|
||||
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
|
||||
if component.usage_validation:
|
||||
value = component.usage_validation(value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
BASE_PIN_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ArduinoInternalGPIOPin),
|
||||
cv.Required(CONF_NUMBER): validate_gpio_pin,
|
||||
cv.Optional(CONF_MODE, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ANALOG, default=False): cv.boolean,
|
||||
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
},
|
||||
)
|
||||
|
||||
BASE_PIN_SCHEMA.add_extra(validate_gpio_usage)
|
||||
|
||||
|
||||
async def component_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
105
esphome/components/libretiny/gpio_arduino.cpp
Normal file
105
esphome/components/libretiny/gpio_arduino.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "gpio_arduino.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny {
|
||||
|
||||
static const char *const TAG = "lt.gpio";
|
||||
|
||||
static int IRAM_ATTR flags_to_mode(gpio::Flags flags) {
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
return INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
return OUTPUT;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
return INPUT_PULLUP;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
|
||||
return INPUT_PULLDOWN;
|
||||
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
return OUTPUT_OPEN_DRAIN;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct ISRPinArg {
|
||||
uint8_t pin;
|
||||
bool inverted;
|
||||
};
|
||||
|
||||
ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = pin_;
|
||||
arg->inverted = inverted_;
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
PinStatus arduino_mode = (PinStatus) 255;
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
arduino_mode = inverted_ ? FALLING : RISING;
|
||||
break;
|
||||
case gpio::INTERRUPT_FALLING_EDGE:
|
||||
arduino_mode = inverted_ ? RISING : FALLING;
|
||||
break;
|
||||
case gpio::INTERRUPT_ANY_EDGE:
|
||||
arduino_mode = CHANGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_LOW_LEVEL:
|
||||
arduino_mode = inverted_ ? HIGH : LOW;
|
||||
break;
|
||||
case gpio::INTERRUPT_HIGH_LEVEL:
|
||||
arduino_mode = inverted_ ? LOW : HIGH;
|
||||
break;
|
||||
}
|
||||
|
||||
attachInterruptParam(pin_, func, arduino_mode, arg);
|
||||
}
|
||||
|
||||
void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
pinMode(pin_, flags_to_mode(flags)); // NOLINT
|
||||
}
|
||||
|
||||
std::string ArduinoInternalGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "%u", pin_);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool ArduinoInternalGPIOPin::digital_read() {
|
||||
return bool(digitalRead(pin_)) ^ inverted_; // NOLINT
|
||||
}
|
||||
void ArduinoInternalGPIOPin::digital_write(bool value) {
|
||||
digitalWrite(pin_, value ^ inverted_); // NOLINT
|
||||
}
|
||||
void ArduinoInternalGPIOPin::detach_interrupt() const {
|
||||
detachInterrupt(pin_); // NOLINT
|
||||
}
|
||||
|
||||
} // namespace libretiny
|
||||
|
||||
using namespace libretiny;
|
||||
|
||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
return bool(digitalRead(arg->pin)) ^ arg->inverted; // NOLINT
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
digitalWrite(arg->pin, value ^ arg->inverted); // NOLINT
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
detachInterrupt(arg->pin);
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
pinMode(arg->pin, flags_to_mode(flags)); // NOLINT
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
36
esphome/components/libretiny/gpio_arduino.h
Normal file
36
esphome/components/libretiny/gpio_arduino.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny {
|
||||
|
||||
class ArduinoInternalGPIOPin : public InternalGPIOPin {
|
||||
public:
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
|
||||
void setup() override { pin_mode(flags_); }
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
void detach_interrupt() const override;
|
||||
ISRInternalGPIOPin to_isr() const override;
|
||||
uint8_t get_pin() const override { return pin_; }
|
||||
bool is_inverted() const override { return inverted_; }
|
||||
|
||||
protected:
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
uint8_t pin_;
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace libretiny
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
29
esphome/components/libretiny/lt_component.cpp
Normal file
29
esphome/components/libretiny/lt_component.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "lt_component.h"
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny {
|
||||
|
||||
static const char *const TAG = "lt.component";
|
||||
|
||||
void LTComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LibreTiny:");
|
||||
ESP_LOGCONFIG(TAG, " Version: %s", LT_BANNER_STR + 10);
|
||||
ESP_LOGCONFIG(TAG, " Loglevel: %u", LT_LOGLEVEL);
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_ != nullptr) {
|
||||
this->version_->publish_state(LT_BANNER_STR + 10);
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
}
|
||||
|
||||
float LTComponent::get_setup_priority() const { return setup_priority::LATE; }
|
||||
|
||||
} // namespace libretiny
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
36
esphome/components/libretiny/lt_component.h
Normal file
36
esphome/components/libretiny/lt_component.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny {
|
||||
|
||||
class LTComponent : public Component {
|
||||
public:
|
||||
float get_setup_priority() const override;
|
||||
void dump_config() override;
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void set_version_sensor(text_sensor::TextSensor *version) { version_ = version; }
|
||||
#endif // USE_TEXT_SENSOR
|
||||
|
||||
protected:
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *version_{nullptr};
|
||||
#endif // USE_TEXT_SENSOR
|
||||
};
|
||||
|
||||
} // namespace libretiny
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
182
esphome/components/libretiny/preferences.cpp
Normal file
182
esphome/components/libretiny/preferences.cpp
Normal file
|
@ -0,0 +1,182 @@
|
|||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <flashdb.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny {
|
||||
|
||||
static const char *const TAG = "lt.preferences";
|
||||
|
||||
struct NVSData {
|
||||
std::string key;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
class LibreTinyPreferenceBackend : public ESPPreferenceBackend {
|
||||
public:
|
||||
std::string key;
|
||||
fdb_kvdb_t db;
|
||||
fdb_blob_t blob;
|
||||
|
||||
bool save(const uint8_t *data, size_t len) override {
|
||||
// try find in pending saves and update that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == key) {
|
||||
obj.data.assign(data, data + len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
NVSData save{};
|
||||
save.key = key;
|
||||
save.data.assign(data, data + len);
|
||||
s_pending_save.emplace_back(save);
|
||||
ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %d", key.c_str(), len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load(uint8_t *data, size_t len) override {
|
||||
// try find in pending saves and load from that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == key) {
|
||||
if (obj.data.size() != len) {
|
||||
// size mismatch
|
||||
return false;
|
||||
}
|
||||
memcpy(data, obj.data.data(), len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
fdb_blob_make(blob, data, len);
|
||||
size_t actual_len = fdb_kv_get_blob(db, key.c_str(), blob);
|
||||
if (actual_len != len) {
|
||||
ESP_LOGVV(TAG, "NVS length does not match (%u!=%u)", actual_len, len);
|
||||
return false;
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %d", key.c_str(), len);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class LibreTinyPreferences : public ESPPreferences {
|
||||
public:
|
||||
struct fdb_kvdb db;
|
||||
struct fdb_blob blob;
|
||||
|
||||
void open() {
|
||||
//
|
||||
fdb_err_t err = fdb_kvdb_init(&db, "esphome", "kvs", NULL, NULL);
|
||||
if (err != FDB_NO_ERR) {
|
||||
LT_E("fdb_kvdb_init(...) failed: %d", err);
|
||||
} else {
|
||||
LT_I("Preferences initialized");
|
||||
}
|
||||
}
|
||||
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
|
||||
return make_preference(length, type);
|
||||
}
|
||||
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
|
||||
auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
pref->db = &db;
|
||||
pref->blob = &blob;
|
||||
|
||||
uint32_t keyval = type;
|
||||
pref->key = str_sprintf("%u", keyval);
|
||||
|
||||
return ESPPreferenceObject(pref);
|
||||
}
|
||||
|
||||
bool sync() override {
|
||||
if (s_pending_save.empty())
|
||||
return true;
|
||||
|
||||
ESP_LOGD(TAG, "Saving %d preferences to flash...", s_pending_save.size());
|
||||
// goal try write all pending saves even if one fails
|
||||
int cached = 0, written = 0, failed = 0;
|
||||
fdb_err_t last_err = FDB_NO_ERR;
|
||||
std::string last_key{};
|
||||
|
||||
// go through vector from back to front (makes erase easier/more efficient)
|
||||
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
|
||||
const auto &save = s_pending_save[i];
|
||||
ESP_LOGVV(TAG, "Checking if FDB data %s has changed", save.key.c_str());
|
||||
if (is_changed(&db, save)) {
|
||||
ESP_LOGV(TAG, "sync: key: %s, len: %d", save.key.c_str(), save.data.size());
|
||||
fdb_blob_make(&blob, save.data.data(), save.data.size());
|
||||
fdb_err_t err = fdb_kv_set_blob(&db, save.key.c_str(), &blob);
|
||||
if (err != FDB_NO_ERR) {
|
||||
ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%u) failed: %d", save.key.c_str(), save.data.size(), err);
|
||||
failed++;
|
||||
last_err = err;
|
||||
last_key = save.key;
|
||||
continue;
|
||||
}
|
||||
written++;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "FDB data not changed; skipping %s len=%u", save.key.c_str(), save.data.size());
|
||||
cached++;
|
||||
}
|
||||
s_pending_save.erase(s_pending_save.begin() + i);
|
||||
}
|
||||
ESP_LOGD(TAG, "Saving %d preferences to flash: %d cached, %d written, %d failed", cached + written + failed, cached,
|
||||
written, failed);
|
||||
if (failed > 0) {
|
||||
ESP_LOGE(TAG, "Error saving %d preferences to flash. Last error=%d for key=%s", failed, last_err,
|
||||
last_key.c_str());
|
||||
}
|
||||
|
||||
return failed == 0;
|
||||
}
|
||||
|
||||
bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) {
|
||||
NVSData stored_data{};
|
||||
struct fdb_kv kv;
|
||||
fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv);
|
||||
if (kvp == nullptr) {
|
||||
ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str());
|
||||
return true;
|
||||
}
|
||||
stored_data.data.reserve(kv.value_len);
|
||||
fdb_blob_make(&blob, stored_data.data.data(), kv.value_len);
|
||||
size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob);
|
||||
if (actual_len != kv.value_len) {
|
||||
ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len);
|
||||
return true;
|
||||
}
|
||||
return to_save.data != stored_data.data;
|
||||
}
|
||||
|
||||
bool reset() override {
|
||||
ESP_LOGD(TAG, "Cleaning up preferences in flash...");
|
||||
s_pending_save.clear();
|
||||
|
||||
fdb_kv_set_default(&db);
|
||||
fdb_kvdb_deinit(&db);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void setup_preferences() {
|
||||
auto *prefs = new LibreTinyPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
prefs->open();
|
||||
global_preferences = prefs;
|
||||
}
|
||||
|
||||
} // namespace libretiny
|
||||
|
||||
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
13
esphome/components/libretiny/preferences.h
Normal file
13
esphome/components/libretiny/preferences.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny {
|
||||
|
||||
void setup_preferences();
|
||||
|
||||
} // namespace libretiny
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
31
esphome/components/libretiny/text_sensor.py
Normal file
31
esphome/components/libretiny/text_sensor.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import (
|
||||
CONF_VERSION,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_CELLPHONE_ARROW_DOWN,
|
||||
)
|
||||
|
||||
from .const import CONF_LIBRETINY, LTComponent
|
||||
|
||||
DEPENDENCIES = ["libretiny"]
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LIBRETINY): cv.use_id(LTComponent),
|
||||
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
icon=ICON_CELLPHONE_ARROW_DOWN,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
lt_component = await cg.get_variable(config[CONF_LIBRETINY])
|
||||
|
||||
if CONF_VERSION in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_VERSION])
|
||||
cg.add(lt_component.set_version_sensor(sens))
|
1
esphome/components/libretiny_pwm/__init__.py
Normal file
1
esphome/components/libretiny_pwm/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@kuba2k2"]
|
53
esphome/components/libretiny_pwm/libretiny_pwm.cpp
Normal file
53
esphome/components/libretiny_pwm/libretiny_pwm.cpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#include "libretiny_pwm.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny_pwm {
|
||||
|
||||
static const char *const TAG = "libretiny.pwm";
|
||||
|
||||
void LibreTinyPWM::write_state(float state) {
|
||||
if (!this->initialized_) {
|
||||
ESP_LOGW(TAG, "LibreTinyPWM output hasn't been initialized yet!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->pin_->is_inverted())
|
||||
state = 1.0f - state;
|
||||
|
||||
this->duty_ = state;
|
||||
const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1;
|
||||
const float duty_rounded = roundf(state * max_duty);
|
||||
auto duty = static_cast<uint32_t>(duty_rounded);
|
||||
|
||||
analogWrite(this->pin_->get_pin(), duty); // NOLINT
|
||||
}
|
||||
|
||||
void LibreTinyPWM::setup() {
|
||||
this->update_frequency(this->frequency_);
|
||||
this->turn_off();
|
||||
}
|
||||
|
||||
void LibreTinyPWM::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "PWM Output:");
|
||||
LOG_PIN(" Pin ", this->pin_);
|
||||
ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_);
|
||||
}
|
||||
|
||||
void LibreTinyPWM::update_frequency(float frequency) {
|
||||
this->frequency_ = frequency;
|
||||
// force changing the frequency by removing PWM mode
|
||||
this->pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
analogWriteResolution(this->bit_depth_); // NOLINT
|
||||
analogWriteFrequency(frequency); // NOLINT
|
||||
this->initialized_ = true;
|
||||
// re-apply duty
|
||||
this->write_state(this->duty_);
|
||||
}
|
||||
|
||||
} // namespace libretiny_pwm
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
55
esphome/components/libretiny_pwm/libretiny_pwm.h
Normal file
55
esphome/components/libretiny_pwm/libretiny_pwm.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny_pwm {
|
||||
|
||||
class LibreTinyPWM : public output::FloatOutput, public Component {
|
||||
public:
|
||||
explicit LibreTinyPWM(InternalGPIOPin *pin) : pin_(pin) {}
|
||||
|
||||
void set_frequency(float frequency) { this->frequency_ = frequency; }
|
||||
/// Dynamically change frequency at runtime
|
||||
void update_frequency(float frequency) override;
|
||||
|
||||
/// Setup LibreTinyPWM.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
/// HARDWARE setup priority
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
/// Override FloatOutput's write_state.
|
||||
void write_state(float state) override;
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *pin_;
|
||||
uint8_t bit_depth_{10};
|
||||
float frequency_{};
|
||||
float duty_{0.0f};
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFrequencyAction : public Action<Ts...> {
|
||||
public:
|
||||
SetFrequencyAction(LibreTinyPWM *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(float, frequency);
|
||||
|
||||
void play(Ts... x) {
|
||||
float freq = this->frequency_.value(x...);
|
||||
this->parent_->update_frequency(freq);
|
||||
}
|
||||
|
||||
protected:
|
||||
LibreTinyPWM *parent_;
|
||||
};
|
||||
|
||||
} // namespace libretiny_pwm
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
49
esphome/components/libretiny_pwm/output.py
Normal file
49
esphome/components/libretiny_pwm/output.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
from esphome import pins, automation
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import (
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_PIN,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["libretiny"]
|
||||
|
||||
libretinypwm_ns = cg.esphome_ns.namespace("libretiny_pwm")
|
||||
LibreTinyPWM = libretinypwm_ns.class_("LibreTinyPWM", output.FloatOutput, cg.Component)
|
||||
SetFrequencyAction = libretinypwm_ns.class_("SetFrequencyAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
gpio = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
var = cg.new_Pvariable(config[CONF_ID], gpio)
|
||||
await cg.register_component(var, config)
|
||||
await output.register_output(var, config)
|
||||
cg.add(var.set_frequency(config[CONF_FREQUENCY]))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"output.libretiny_pwm.set_frequency",
|
||||
SetFrequencyAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LibreTinyPWM),
|
||||
cv.Required(CONF_FREQUENCY): cv.templatable(cv.int_),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def libretiny_pwm_set_frequency_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_FREQUENCY], args, float)
|
||||
cg.add(var.set_frequency(template_))
|
||||
return var
|
|
@ -17,6 +17,8 @@ from esphome.const import (
|
|||
CONF_TAG,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TX_BUFFER_SIZE,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_RTL87XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
|
@ -31,6 +33,11 @@ from esphome.components.esp32.const import (
|
|||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C6,
|
||||
)
|
||||
from esphome.components.libretiny import get_libretiny_component, get_libretiny_family
|
||||
from esphome.components.libretiny.const import (
|
||||
COMPONENT_BK72XX,
|
||||
COMPONENT_RTL87XX,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
logger_ns = cg.esphome_ns.namespace("logger")
|
||||
|
@ -70,6 +77,7 @@ UART2 = "UART2"
|
|||
UART0_SWAP = "UART0_SWAP"
|
||||
USB_SERIAL_JTAG = "USB_SERIAL_JTAG"
|
||||
USB_CDC = "USB_CDC"
|
||||
DEFAULT = "DEFAULT"
|
||||
|
||||
UART_SELECTION_ESP32 = {
|
||||
VARIANT_ESP32: [UART0, UART1, UART2],
|
||||
|
@ -82,6 +90,11 @@ UART_SELECTION_ESP32 = {
|
|||
|
||||
UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
|
||||
|
||||
UART_SELECTION_LIBRETINY = {
|
||||
COMPONENT_BK72XX: [DEFAULT, UART1, UART2],
|
||||
COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2],
|
||||
}
|
||||
|
||||
ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG]
|
||||
|
||||
UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1]
|
||||
|
@ -93,6 +106,7 @@ HARDWARE_UART_TO_UART_SELECTION = {
|
|||
UART2: logger_ns.UART_SELECTION_UART2,
|
||||
USB_CDC: logger_ns.UART_SELECTION_USB_CDC,
|
||||
USB_SERIAL_JTAG: logger_ns.UART_SELECTION_USB_SERIAL_JTAG,
|
||||
DEFAULT: logger_ns.UART_SELECTION_DEFAULT,
|
||||
}
|
||||
|
||||
HARDWARE_UART_TO_SERIAL = {
|
||||
|
@ -100,6 +114,7 @@ HARDWARE_UART_TO_SERIAL = {
|
|||
UART0_SWAP: cg.global_ns.Serial,
|
||||
UART1: cg.global_ns.Serial1,
|
||||
UART2: cg.global_ns.Serial2,
|
||||
DEFAULT: cg.global_ns.Serial,
|
||||
}
|
||||
|
||||
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
||||
|
@ -116,6 +131,13 @@ def uart_selection(value):
|
|||
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
|
||||
if CORE.is_rp2040:
|
||||
return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value)
|
||||
if CORE.is_libretiny:
|
||||
family = get_libretiny_family()
|
||||
if family in UART_SELECTION_LIBRETINY:
|
||||
return cv.one_of(*UART_SELECTION_LIBRETINY[family], upper=True)(value)
|
||||
component = get_libretiny_component()
|
||||
if component in UART_SELECTION_LIBRETINY:
|
||||
return cv.one_of(*UART_SELECTION_LIBRETINY[component], upper=True)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
@ -148,8 +170,18 @@ CONFIG_SCHEMA = cv.All(
|
|||
esp8266=UART0,
|
||||
esp32=UART0,
|
||||
rp2040=USB_CDC,
|
||||
bk72xx=DEFAULT,
|
||||
rtl87xx=DEFAULT,
|
||||
): cv.All(
|
||||
cv.only_on([PLATFORM_ESP8266, PLATFORM_ESP32, PLATFORM_RP2040]),
|
||||
cv.only_on(
|
||||
[
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_RTL87XX,
|
||||
]
|
||||
),
|
||||
uart_selection,
|
||||
),
|
||||
cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
|
||||
|
|
|
@ -158,6 +158,7 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate
|
|||
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
|
||||
}
|
||||
|
||||
#ifndef USE_LIBRETINY
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
#ifdef USE_ARDUINO
|
||||
|
@ -266,12 +267,58 @@ void Logger::pre_setup() {
|
|||
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
#else // USE_LIBRETINY
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
#if LT_HW_UART0
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial0;
|
||||
Serial0.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART1
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART2
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ != UART_SELECTION_DEFAULT) {
|
||||
ESP_LOGW(TAG, " The chosen logger UART port is not available on this board."
|
||||
"The default port was used instead.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// change lt_log() port to match default Serial
|
||||
if (this->uart_ == UART_SELECTION_DEFAULT) {
|
||||
this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1);
|
||||
lt_log_set_port(LT_UART_DEFAULT_SERIAL);
|
||||
} else {
|
||||
lt_log_set_port(this->uart_ - 1);
|
||||
}
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
#endif // USE_LIBRETINY
|
||||
|
||||
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
|
||||
void Logger::set_log_level(const std::string &tag, int log_level) {
|
||||
this->log_levels_.push_back(LogLevelOverride{tag, log_level});
|
||||
}
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||
UARTSelection Logger::get_uart() const { return this->uart_; }
|
||||
#endif
|
||||
|
||||
|
@ -299,15 +346,18 @@ const char *const UART_SELECTIONS[] = {
|
|||
#endif // USE_ESP32
|
||||
#ifdef USE_ESP8266
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
|
||||
#endif
|
||||
#endif // USE_ESP8266
|
||||
#ifdef USE_RP2040
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
|
||||
#endif // USE_ESP8266
|
||||
#endif // USE_RP2040
|
||||
#ifdef USE_LIBRETINY
|
||||
const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"};
|
||||
#endif // USE_LIBRETINY
|
||||
void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Logger:");
|
||||
ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
|
||||
ESP_LOGCONFIG(TAG, " Log Baud Rate: %" PRIu32, this->baud_rate_);
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||
ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -25,12 +25,18 @@ namespace esphome {
|
|||
|
||||
namespace logger {
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||
/** Enum for logging UART selection
|
||||
*
|
||||
* Advanced configuration (pin selection, etc) is not supported.
|
||||
*/
|
||||
enum UARTSelection {
|
||||
#ifdef USE_LIBRETINY
|
||||
UART_SELECTION_DEFAULT = 0,
|
||||
UART_SELECTION_UART0,
|
||||
UART_SELECTION_UART1,
|
||||
UART_SELECTION_UART2,
|
||||
#else
|
||||
UART_SELECTION_UART0 = 0,
|
||||
UART_SELECTION_UART1,
|
||||
#if defined(USE_ESP32)
|
||||
|
@ -53,8 +59,9 @@ enum UARTSelection {
|
|||
#ifdef USE_RP2040
|
||||
UART_SELECTION_USB_CDC,
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_LIBRETINY
|
||||
};
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY
|
||||
|
||||
class Logger : public Component {
|
||||
public:
|
||||
|
@ -69,7 +76,7 @@ class Logger : public Component {
|
|||
#ifdef USE_ESP_IDF
|
||||
uart_port_t get_uart_num() const { return uart_num_; }
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
|
||||
/// Get the UART used by the logger.
|
||||
UARTSelection get_uart() const;
|
||||
|
@ -146,6 +153,9 @@ class Logger : public Component {
|
|||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
|
||||
UARTSelection uart_{UART_SELECTION_UART0};
|
||||
#endif
|
||||
#ifdef USE_LIBRETINY
|
||||
UARTSelection uart_{UART_SELECTION_DEFAULT};
|
||||
#endif
|
||||
#ifdef USE_ARDUINO
|
||||
Stream *hw_serial_{nullptr};
|
||||
#endif
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
#define MD5_CTX_TYPE br_md5_context
|
||||
#endif
|
||||
|
||||
#if defined(USE_LIBRETINY)
|
||||
#include <MD5.h>
|
||||
#define MD5_CTX_TYPE LT_MD5_CTX_T
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace md5 {
|
||||
|
||||
|
|
|
@ -44,6 +44,9 @@ void MDNSComponent::compile_records_() {
|
|||
#endif
|
||||
#ifdef USE_RP2040
|
||||
platform = "RP2040";
|
||||
#endif
|
||||
#ifdef USE_LIBRETINY
|
||||
platform = lt_cpu_get_model_name();
|
||||
#endif
|
||||
if (platform != nullptr) {
|
||||
service.txt_records.push_back({"platform", platform});
|
||||
|
|
43
esphome/components/mdns/mdns_libretiny.cpp
Normal file
43
esphome/components/mdns/mdns_libretiny.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "mdns_component.h"
|
||||
|
||||
#include <mDNS.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
this->compile_records_();
|
||||
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : this->services_) {
|
||||
// Strip the leading underscore from the proto and service_type. While it is
|
||||
// part of the wire protocol to have an underscore, and for example ESP-IDF
|
||||
// expects the underscore to be there, the ESP8266 implementation always adds
|
||||
// the underscore itself.
|
||||
auto *proto = service.proto.c_str();
|
||||
while (*proto == '_') {
|
||||
proto++;
|
||||
}
|
||||
auto *service_type = service.service_type.c_str();
|
||||
while (*service_type == '_') {
|
||||
service_type++;
|
||||
}
|
||||
MDNS.addService(service_type, proto, service.port);
|
||||
for (const auto &record : service.txt_records) {
|
||||
MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MDNSComponent::on_shutdown() {}
|
||||
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
|
@ -41,7 +41,14 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
{
|
||||
cv.GenerateID(): cv.declare_id(OTAComponent),
|
||||
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
||||
cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232, rp2040=2040): cv.port,
|
||||
cv.SplitDefault(
|
||||
CONF_PORT,
|
||||
esp8266=8266,
|
||||
esp32=3232,
|
||||
rp2040=2040,
|
||||
bk72xx=8892,
|
||||
rtl87xx=8892,
|
||||
): cv.port,
|
||||
cv.Optional(CONF_PASSWORD): cv.string,
|
||||
cv.Optional(
|
||||
CONF_REBOOT_TIMEOUT, default="5min"
|
||||
|
|
46
esphome/components/ota/ota_backend_arduino_libretiny.cpp
Normal file
46
esphome/components/ota/ota_backend_arduino_libretiny.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "ota_backend_arduino_libretiny.h"
|
||||
#include "ota_component.h"
|
||||
#include "ota_backend.h"
|
||||
|
||||
#include <Update.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ota {
|
||||
|
||||
OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) {
|
||||
bool ret = Update.begin(image_size, U_FLASH);
|
||||
if (ret) {
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
uint8_t error = Update.getError();
|
||||
if (error == UPDATE_ERROR_SIZE)
|
||||
return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE;
|
||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
|
||||
|
||||
OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) {
|
||||
size_t written = Update.write(data, len);
|
||||
if (written != len) {
|
||||
return OTA_RESPONSE_ERROR_WRITING_FLASH;
|
||||
}
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoLibreTinyOTABackend::end() {
|
||||
if (!Update.end())
|
||||
return OTA_RESPONSE_ERROR_UPDATE_END;
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
void ArduinoLibreTinyOTABackend::abort() { Update.abort(); }
|
||||
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
24
esphome/components/ota/ota_backend_arduino_libretiny.h
Normal file
24
esphome/components/ota/ota_backend_arduino_libretiny.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "ota_component.h"
|
||||
#include "ota_backend.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ota {
|
||||
|
||||
class ArduinoLibreTinyOTABackend : public OTABackend {
|
||||
public:
|
||||
OTAResponseTypes begin(size_t image_size) override;
|
||||
void set_update_md5(const char *md5) override;
|
||||
OTAResponseTypes write(uint8_t *data, size_t len) override;
|
||||
OTAResponseTypes end() override;
|
||||
void abort() override;
|
||||
bool supports_compression() override { return false; }
|
||||
};
|
||||
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
|
@ -3,6 +3,7 @@
|
|||
#include "ota_backend_arduino_esp32.h"
|
||||
#include "ota_backend_arduino_esp8266.h"
|
||||
#include "ota_backend_arduino_rp2040.h"
|
||||
#include "ota_backend_arduino_libretiny.h"
|
||||
#include "ota_backend_esp_idf.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
@ -39,6 +40,9 @@ std::unique_ptr<OTABackend> make_ota_backend() {
|
|||
#ifdef USE_RP2040
|
||||
return make_unique<ArduinoRP2040OTABackend>();
|
||||
#endif // USE_RP2040
|
||||
#ifdef USE_LIBRETINY
|
||||
return make_unique<ArduinoLibreTinyOTABackend>();
|
||||
#endif
|
||||
}
|
||||
|
||||
OTAComponent::OTAComponent() { global_ota_component = this; }
|
||||
|
|
|
@ -31,7 +31,11 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
|
|||
cv.percentage_int, cv.Range(min=0)
|
||||
),
|
||||
cv.SplitDefault(
|
||||
CONF_BUFFER_SIZE, esp32="10000b", esp8266="1000b"
|
||||
CONF_BUFFER_SIZE,
|
||||
esp32="10000b",
|
||||
esp8266="1000b",
|
||||
bk72xx="1000b",
|
||||
rtl87xx="1000b",
|
||||
): cv.validate_bytes,
|
||||
cv.Optional(CONF_FILTER, default="50us"): cv.All(
|
||||
cv.positive_time_period_microseconds,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
namespace esphome {
|
||||
namespace remote_receiver {
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
|
||||
struct RemoteReceiverComponentStore {
|
||||
static void gpio_intr(RemoteReceiverComponentStore *arg);
|
||||
|
||||
|
@ -55,7 +55,7 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
|
|||
esp_err_t error_code_{ESP_OK};
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
|
||||
RemoteReceiverComponentStore store_;
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
#endif
|
||||
|
|
122
esphome/components/remote_receiver/remote_receiver_libretiny.cpp
Normal file
122
esphome/components/remote_receiver/remote_receiver_libretiny.cpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
#include "remote_receiver.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_receiver {
|
||||
|
||||
static const char *const TAG = "remote_receiver.libretiny";
|
||||
|
||||
void IRAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) {
|
||||
const uint32_t now = micros();
|
||||
// If the lhs is 1 (rising edge) we should write to an uneven index and vice versa
|
||||
const uint32_t next = (arg->buffer_write_at + 1) % arg->buffer_size;
|
||||
const bool level = arg->pin.digital_read();
|
||||
if (level != next % 2)
|
||||
return;
|
||||
|
||||
// If next is buffer_read, we have hit an overflow
|
||||
if (next == arg->buffer_read_at)
|
||||
return;
|
||||
|
||||
const uint32_t last_change = arg->buffer[arg->buffer_write_at];
|
||||
const uint32_t time_since_change = now - last_change;
|
||||
if (time_since_change <= arg->filter_us)
|
||||
return;
|
||||
|
||||
arg->buffer[arg->buffer_write_at = next] = now;
|
||||
}
|
||||
|
||||
void RemoteReceiverComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Remote Receiver...");
|
||||
this->pin_->setup();
|
||||
auto &s = this->store_;
|
||||
s.filter_us = this->filter_us_;
|
||||
s.pin = this->pin_->to_isr();
|
||||
s.buffer_size = this->buffer_size_;
|
||||
|
||||
this->high_freq_.start();
|
||||
if (s.buffer_size % 2 != 0) {
|
||||
// Make sure divisible by two. This way, we know that every 0bxxx0 index is a space and every 0bxxx1 index is a mark
|
||||
s.buffer_size++;
|
||||
}
|
||||
|
||||
s.buffer = new uint32_t[s.buffer_size];
|
||||
void *buf = (void *) s.buffer;
|
||||
memset(buf, 0, s.buffer_size * sizeof(uint32_t));
|
||||
|
||||
// First index is a space.
|
||||
if (this->pin_->digital_read()) {
|
||||
s.buffer_write_at = s.buffer_read_at = 1;
|
||||
} else {
|
||||
s.buffer_write_at = s.buffer_read_at = 0;
|
||||
}
|
||||
this->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
|
||||
}
|
||||
void RemoteReceiverComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Remote Receiver:");
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
if (this->pin_->digital_read()) {
|
||||
ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to "
|
||||
"invert the signal using 'inverted: True' in the pin schema!");
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Buffer Size: %u", this->buffer_size_);
|
||||
ESP_LOGCONFIG(TAG, " Tolerance: %u%%", this->tolerance_);
|
||||
ESP_LOGCONFIG(TAG, " Filter out pulses shorter than: %u us", this->filter_us_);
|
||||
ESP_LOGCONFIG(TAG, " Signal is done after %u us of no changes", this->idle_us_);
|
||||
}
|
||||
|
||||
void RemoteReceiverComponent::loop() {
|
||||
auto &s = this->store_;
|
||||
|
||||
// copy write at to local variables, as it's volatile
|
||||
const uint32_t write_at = s.buffer_write_at;
|
||||
const uint32_t dist = (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size;
|
||||
// signals must at least one rising and one leading edge
|
||||
if (dist <= 1)
|
||||
return;
|
||||
const uint32_t now = micros();
|
||||
if (now - s.buffer[write_at] < this->idle_us_) {
|
||||
// The last change was fewer than the configured idle time ago.
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "read_at=%u write_at=%u dist=%u now=%u end=%u", s.buffer_read_at, write_at, dist, now,
|
||||
s.buffer[write_at]);
|
||||
|
||||
// Skip first value, it's from the previous idle level
|
||||
s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size;
|
||||
uint32_t prev = s.buffer_read_at;
|
||||
s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size;
|
||||
const uint32_t reserve_size = 1 + (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size;
|
||||
this->temp_.clear();
|
||||
this->temp_.reserve(reserve_size);
|
||||
int32_t multiplier = s.buffer_read_at % 2 == 0 ? 1 : -1;
|
||||
|
||||
for (uint32_t i = 0; prev != write_at; i++) {
|
||||
int32_t delta = s.buffer[s.buffer_read_at] - s.buffer[prev];
|
||||
if (uint32_t(delta) >= this->idle_us_) {
|
||||
// already found a space longer than idle. There must have been two pulses
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, " i=%u buffer[%u]=%u - buffer[%u]=%u -> %d", i, s.buffer_read_at, s.buffer[s.buffer_read_at], prev,
|
||||
s.buffer[prev], multiplier * delta);
|
||||
this->temp_.push_back(multiplier * delta);
|
||||
prev = s.buffer_read_at;
|
||||
s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size;
|
||||
multiplier *= -1;
|
||||
}
|
||||
s.buffer_read_at = (s.buffer_size + s.buffer_read_at - 1) % s.buffer_size;
|
||||
this->temp_.push_back(this->idle_us_ * multiplier);
|
||||
|
||||
this->call_listeners_dumpers_();
|
||||
}
|
||||
|
||||
} // namespace remote_receiver
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
|
@ -28,7 +28,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
|||
|
||||
protected:
|
||||
void send_internal(uint32_t send_times, uint32_t send_wait) override;
|
||||
#ifdef USE_ESP8266
|
||||
#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
|
||||
void calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, uint32_t *off_time_period);
|
||||
|
||||
void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec);
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
#include "remote_transmitter.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_transmitter {
|
||||
|
||||
static const char *const TAG = "remote_transmitter";
|
||||
|
||||
void RemoteTransmitterComponent::setup() {
|
||||
this->pin_->setup();
|
||||
this->pin_->digital_write(false);
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Remote Transmitter...");
|
||||
ESP_LOGCONFIG(TAG, " Carrier Duty: %u%%", this->carrier_duty_percent_);
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period,
|
||||
uint32_t *off_time_period) {
|
||||
if (carrier_frequency == 0) {
|
||||
*on_time_period = 0;
|
||||
*off_time_period = 0;
|
||||
return;
|
||||
}
|
||||
uint32_t period = (1000000UL + carrier_frequency / 2) / carrier_frequency; // round(1000000/freq)
|
||||
period = std::max(uint32_t(1), period);
|
||||
*on_time_period = (period * this->carrier_duty_percent_) / 100;
|
||||
*off_time_period = period - *on_time_period;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::await_target_time_() {
|
||||
const uint32_t current_time = micros();
|
||||
if (this->target_time_ == 0) {
|
||||
this->target_time_ = current_time;
|
||||
} else {
|
||||
while (this->target_time_ > micros()) {
|
||||
// busy loop that ensures micros is constantly called
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) {
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(true);
|
||||
|
||||
const uint32_t target = this->target_time_ + usec;
|
||||
if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) {
|
||||
while (true) { // Modulate with carrier frequency
|
||||
this->target_time_ += on_time;
|
||||
if (this->target_time_ >= target)
|
||||
break;
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
this->target_time_ += off_time;
|
||||
if (this->target_time_ >= target)
|
||||
break;
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
this->target_time_ = target;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::space_(uint32_t usec) {
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(false);
|
||||
this->target_time_ += usec;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
ESP_LOGD(TAG, "Sending remote code...");
|
||||
uint32_t on_time, off_time;
|
||||
this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time);
|
||||
this->target_time_ = 0;
|
||||
for (uint32_t i = 0; i < send_times; i++) {
|
||||
InterruptLock lock;
|
||||
for (int32_t item : this->temp_.get_data()) {
|
||||
if (item > 0) {
|
||||
const auto length = uint32_t(item);
|
||||
this->mark_(on_time, off_time, length);
|
||||
} else {
|
||||
const auto length = uint32_t(-item);
|
||||
this->space_(length);
|
||||
}
|
||||
App.feed_wdt();
|
||||
}
|
||||
this->await_target_time_(); // wait for duration of last pulse
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
if (i + 1 < send_times)
|
||||
this->target_time_ += send_wait;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace remote_transmitter
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
|
@ -1,6 +1,7 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ANALOG,
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_INVERTED,
|
||||
|
@ -76,8 +77,6 @@ def validate_supports(value):
|
|||
return value
|
||||
|
||||
|
||||
CONF_ANALOG = "analog"
|
||||
|
||||
RP2040_PIN_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
|
|
51
esphome/components/rtl87xx/__init__.py
Normal file
51
esphome/components/rtl87xx/__init__.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
# This file was auto-generated by libretiny/generate_components.py
|
||||
# Do not modify its contents.
|
||||
# For custom pin validators, put validate_pin() or validate_usage()
|
||||
# in gpio.py file in this directory.
|
||||
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
# in schema.py file in this directory.
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import libretiny
|
||||
from esphome.components.libretiny.const import (
|
||||
COMPONENT_RTL87XX,
|
||||
KEY_COMPONENT_DATA,
|
||||
KEY_LIBRETINY,
|
||||
LibreTinyComponent,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
from .boards import RTL87XX_BOARDS, RTL87XX_BOARD_PINS
|
||||
|
||||
CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = ["libretiny"]
|
||||
|
||||
COMPONENT_DATA = LibreTinyComponent(
|
||||
name=COMPONENT_RTL87XX,
|
||||
boards=RTL87XX_BOARDS,
|
||||
board_pins=RTL87XX_BOARD_PINS,
|
||||
pin_validation=None,
|
||||
usage_validation=None,
|
||||
)
|
||||
|
||||
|
||||
def _set_core_data(config):
|
||||
CORE.data[KEY_LIBRETINY] = {}
|
||||
CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] = COMPONENT_DATA
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = libretiny.BASE_SCHEMA
|
||||
|
||||
PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA
|
||||
|
||||
CONFIG_SCHEMA.prepend_extra(_set_core_data)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
return await libretiny.component_to_code(config)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("rtl87xx", PIN_SCHEMA)
|
||||
async def pin_to_code(config):
|
||||
return await libretiny.gpio.component_pin_to_code(config)
|
1390
esphome/components/rtl87xx/boards.py
Normal file
1390
esphome/components/rtl87xx/boards.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
#include "sntp_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
#include "lwip/apps/sntp.h"
|
||||
#ifdef USE_ESP_IDF
|
||||
#include "esp_sntp.h"
|
||||
|
@ -26,7 +26,7 @@ static const char *const TAG = "sntp";
|
|||
|
||||
void SNTPComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up SNTP...");
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
if (sntp_enabled()) {
|
||||
sntp_stop();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ CODEOWNERS = ["@esphome/core"]
|
|||
|
||||
CONF_IMPLEMENTATION = "implementation"
|
||||
IMPLEMENTATION_LWIP_TCP = "lwip_tcp"
|
||||
IMPLEMENTATION_LWIP_SOCKETS = "lwip_sockets"
|
||||
IMPLEMENTATION_BSD_SOCKETS = "bsd_sockets"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
|
@ -14,9 +15,15 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
esp8266=IMPLEMENTATION_LWIP_TCP,
|
||||
esp32=IMPLEMENTATION_BSD_SOCKETS,
|
||||
rp2040=IMPLEMENTATION_LWIP_TCP,
|
||||
bk72xx=IMPLEMENTATION_LWIP_SOCKETS,
|
||||
rtl87xx=IMPLEMENTATION_LWIP_SOCKETS,
|
||||
host=IMPLEMENTATION_BSD_SOCKETS,
|
||||
): cv.one_of(
|
||||
IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
|
||||
IMPLEMENTATION_LWIP_TCP,
|
||||
IMPLEMENTATION_LWIP_SOCKETS,
|
||||
IMPLEMENTATION_BSD_SOCKETS,
|
||||
lower=True,
|
||||
space="_",
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@ -26,5 +33,7 @@ async def to_code(config):
|
|||
impl = config[CONF_IMPLEMENTATION]
|
||||
if impl == IMPLEMENTATION_LWIP_TCP:
|
||||
cg.add_define("USE_SOCKET_IMPL_LWIP_TCP")
|
||||
elif impl == IMPLEMENTATION_LWIP_SOCKETS:
|
||||
cg.add_define("USE_SOCKET_IMPL_LWIP_SOCKETS")
|
||||
elif impl == IMPLEMENTATION_BSD_SOCKETS:
|
||||
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
|
||||
|
|
|
@ -120,6 +120,35 @@ struct iovec {
|
|||
|
||||
#endif // USE_SOCKET_IMPL_LWIP_TCP
|
||||
|
||||
#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS
|
||||
|
||||
// standard lwIP's compatibility macros will interfere
|
||||
// with Socket class function names - disable the macros
|
||||
// and use real function names instead
|
||||
#undef LWIP_COMPAT_SOCKETS
|
||||
#define LWIP_COMPAT_SOCKETS 0
|
||||
|
||||
#include "lwip/sockets.h"
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
// arduino-esp32 declares a global var called INADDR_NONE which is replaced
|
||||
// by the define
|
||||
#ifdef INADDR_NONE
|
||||
#undef INADDR_NONE
|
||||
#endif
|
||||
// not defined for ESP32
|
||||
using socklen_t = uint32_t;
|
||||
|
||||
#define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL)
|
||||
#define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL)
|
||||
#else // !USE_ESP32
|
||||
#define ESPHOME_INADDR_ANY INADDR_ANY
|
||||
#define ESPHOME_INADDR_NONE INADDR_NONE
|
||||
#endif
|
||||
|
||||
#endif // USE_SOCKET_IMPL_LWIP_SOCKETS
|
||||
|
||||
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
|
||||
#include <cstdint>
|
||||
|
|
115
esphome/components/socket/lwip_sockets_impl.cpp
Normal file
115
esphome/components/socket/lwip_sockets_impl.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#include "socket.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
namespace socket {
|
||||
|
||||
std::string format_sockaddr(const struct sockaddr_storage &storage) {
|
||||
if (storage.ss_family == AF_INET) {
|
||||
const struct sockaddr_in *addr = reinterpret_cast<const struct sockaddr_in *>(&storage);
|
||||
char buf[INET_ADDRSTRLEN];
|
||||
const char *ret = lwip_inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf));
|
||||
if (ret == nullptr)
|
||||
return {};
|
||||
return std::string{buf};
|
||||
}
|
||||
#if LWIP_IPV6
|
||||
else if (storage.ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6 *addr = reinterpret_cast<const struct sockaddr_in6 *>(&storage);
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
const char *ret = lwip_inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf));
|
||||
if (ret == nullptr)
|
||||
return {};
|
||||
return std::string{buf};
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
class LwIPSocketImpl : public Socket {
|
||||
public:
|
||||
LwIPSocketImpl(int fd) : fd_(fd) {}
|
||||
~LwIPSocketImpl() override {
|
||||
if (!closed_) {
|
||||
close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall)
|
||||
}
|
||||
}
|
||||
std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
int fd = lwip_accept(fd_, addr, addrlen);
|
||||
if (fd == -1)
|
||||
return {};
|
||||
return make_unique<LwIPSocketImpl>(fd);
|
||||
}
|
||||
int bind(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_bind(fd_, addr, addrlen); }
|
||||
int close() override {
|
||||
int ret = lwip_close(fd_);
|
||||
closed_ = true;
|
||||
return ret;
|
||||
}
|
||||
int shutdown(int how) override { return lwip_shutdown(fd_, how); }
|
||||
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getpeername(fd_, addr, addrlen); }
|
||||
std::string getpeername() override {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
int err = this->getpeername((struct sockaddr *) &storage, &len);
|
||||
if (err != 0)
|
||||
return {};
|
||||
return format_sockaddr(storage);
|
||||
}
|
||||
int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getsockname(fd_, addr, addrlen); }
|
||||
std::string getsockname() override {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
int err = this->getsockname((struct sockaddr *) &storage, &len);
|
||||
if (err != 0)
|
||||
return {};
|
||||
return format_sockaddr(storage);
|
||||
}
|
||||
int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override {
|
||||
return lwip_getsockopt(fd_, level, optname, optval, optlen);
|
||||
}
|
||||
int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override {
|
||||
return lwip_setsockopt(fd_, level, optname, optval, optlen);
|
||||
}
|
||||
int listen(int backlog) override { return lwip_listen(fd_, backlog); }
|
||||
ssize_t read(void *buf, size_t len) override { return lwip_read(fd_, buf, len); }
|
||||
ssize_t readv(const struct iovec *iov, int iovcnt) override { return lwip_readv(fd_, iov, iovcnt); }
|
||||
ssize_t write(const void *buf, size_t len) override { return lwip_write(fd_, buf, len); }
|
||||
ssize_t send(void *buf, size_t len, int flags) { return lwip_send(fd_, buf, len, flags); }
|
||||
ssize_t writev(const struct iovec *iov, int iovcnt) override { return lwip_writev(fd_, iov, iovcnt); }
|
||||
ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override {
|
||||
return lwip_sendto(fd_, buf, len, flags, to, tolen);
|
||||
}
|
||||
int setblocking(bool blocking) override {
|
||||
int fl = lwip_fcntl(fd_, F_GETFL, 0);
|
||||
if (blocking) {
|
||||
fl &= ~O_NONBLOCK;
|
||||
} else {
|
||||
fl |= O_NONBLOCK;
|
||||
}
|
||||
lwip_fcntl(fd_, F_SETFL, fl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
int fd_;
|
||||
bool closed_ = false;
|
||||
};
|
||||
|
||||
std::unique_ptr<Socket> socket(int domain, int type, int protocol) {
|
||||
int ret = lwip_socket(domain, type, protocol);
|
||||
if (ret == -1)
|
||||
return nullptr;
|
||||
return std::unique_ptr<Socket>{new LwIPSocketImpl(ret)};
|
||||
}
|
||||
|
||||
} // namespace socket
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_SOCKET_IMPL_LWIP_SOCKETS
|
|
@ -48,6 +48,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
}
|
||||
),
|
||||
cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN),
|
||||
cv.only_on(["esp32", "esp8266", "rp2040"]),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@ ESP8266UartComponent = uart_ns.class_(
|
|||
"ESP8266UartComponent", UARTComponent, cg.Component
|
||||
)
|
||||
RP2040UartComponent = uart_ns.class_("RP2040UartComponent", UARTComponent, cg.Component)
|
||||
LibreTinyUARTComponent = uart_ns.class_(
|
||||
"LibreTinyUARTComponent", UARTComponent, cg.Component
|
||||
)
|
||||
|
||||
UARTDevice = uart_ns.class_("UARTDevice")
|
||||
UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
|
||||
|
@ -92,6 +95,8 @@ def _uart_declare_type(value):
|
|||
return cv.declare_id(IDFUARTComponent)(value)
|
||||
if CORE.is_rp2040:
|
||||
return cv.declare_id(RP2040UartComponent)(value)
|
||||
if CORE.is_libretiny:
|
||||
return cv.declare_id(LibreTinyUARTComponent)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
|
168
esphome/components/uart/uart_component_libretiny.cpp
Normal file
168
esphome/components/uart/uart_component_libretiny.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "uart_component_libretiny.h"
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
|
||||
#if LT_ARD_HAS_SOFTSERIAL
|
||||
#include <SoftwareSerial.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace uart {
|
||||
|
||||
static const char *const TAG = "uart.lt";
|
||||
|
||||
static const char *UART_TYPE[] = {
|
||||
"hardware",
|
||||
"software",
|
||||
};
|
||||
|
||||
uint16_t LibreTinyUARTComponent::get_config() {
|
||||
uint16_t config = 0;
|
||||
|
||||
switch (this->parity_) {
|
||||
case UART_CONFIG_PARITY_NONE:
|
||||
config |= SERIAL_PARITY_NONE;
|
||||
break;
|
||||
case UART_CONFIG_PARITY_EVEN:
|
||||
config |= SERIAL_PARITY_EVEN;
|
||||
break;
|
||||
case UART_CONFIG_PARITY_ODD:
|
||||
config |= SERIAL_PARITY_ODD;
|
||||
break;
|
||||
}
|
||||
|
||||
config |= (this->data_bits_ - 4) << 8;
|
||||
config |= 0x10 + (this->stop_bits_ - 1) * 0x20;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
void LibreTinyUARTComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up UART...");
|
||||
|
||||
int8_t tx_pin = tx_pin_ == nullptr ? -1 : tx_pin_->get_pin();
|
||||
int8_t rx_pin = rx_pin_ == nullptr ? -1 : rx_pin_->get_pin();
|
||||
bool tx_inverted = tx_pin_ != nullptr && tx_pin_->is_inverted();
|
||||
bool rx_inverted = rx_pin_ != nullptr && rx_pin_->is_inverted();
|
||||
|
||||
if (false)
|
||||
return;
|
||||
#if LT_HW_UART0
|
||||
else if ((tx_pin == -1 || tx_pin == PIN_SERIAL0_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL0_RX)) {
|
||||
this->serial_ = &Serial0;
|
||||
this->hardware_idx_ = 0;
|
||||
}
|
||||
#endif
|
||||
#if LT_HW_UART1
|
||||
else if ((tx_pin == -1 || tx_pin == PIN_SERIAL1_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL1_RX)) {
|
||||
this->serial_ = &Serial1;
|
||||
this->hardware_idx_ = 1;
|
||||
}
|
||||
#endif
|
||||
#if LT_HW_UART2
|
||||
else if ((tx_pin == -1 || tx_pin == PIN_SERIAL2_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL2_RX)) {
|
||||
this->serial_ = &Serial2;
|
||||
this->hardware_idx_ = 2;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
#if LT_ARD_HAS_SOFTSERIAL
|
||||
this->serial_ = new SoftwareSerial(rx_pin, tx_pin, rx_inverted || tx_inverted);
|
||||
#else
|
||||
this->serial_ = &Serial;
|
||||
ESP_LOGE(TAG, " SoftwareSerial is not implemented for this chip. Only hardware pins are supported:");
|
||||
#if LT_HW_UART0
|
||||
ESP_LOGE(TAG, " TX=%u, RX=%u", PIN_SERIAL0_TX, PIN_SERIAL0_RX);
|
||||
#endif
|
||||
#if LT_HW_UART1
|
||||
ESP_LOGE(TAG, " TX=%u, RX=%u", PIN_SERIAL1_TX, PIN_SERIAL1_RX);
|
||||
#endif
|
||||
#if LT_HW_UART2
|
||||
ESP_LOGE(TAG, " TX=%u, RX=%u", PIN_SERIAL2_TX, PIN_SERIAL2_RX);
|
||||
#endif
|
||||
this->mark_failed();
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
this->serial_->begin(this->baud_rate_, get_config());
|
||||
}
|
||||
|
||||
void LibreTinyUARTComponent::dump_config() {
|
||||
bool is_software = this->hardware_idx_ == -1;
|
||||
ESP_LOGCONFIG(TAG, "UART Bus:");
|
||||
ESP_LOGCONFIG(TAG, " Type: %s", UART_TYPE[is_software]);
|
||||
if (!is_software) {
|
||||
ESP_LOGCONFIG(TAG, " Port number: %d", this->hardware_idx_);
|
||||
}
|
||||
LOG_PIN(" TX Pin: ", tx_pin_);
|
||||
LOG_PIN(" RX Pin: ", rx_pin_);
|
||||
if (this->rx_pin_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_);
|
||||
ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_);
|
||||
ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_)));
|
||||
ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_);
|
||||
this->check_logger_conflict();
|
||||
}
|
||||
|
||||
void LibreTinyUARTComponent::write_array(const uint8_t *data, size_t len) {
|
||||
this->serial_->write(data, len);
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LibreTinyUARTComponent::peek_byte(uint8_t *data) {
|
||||
if (!this->check_read_timeout_())
|
||||
return false;
|
||||
*data = this->serial_->peek();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LibreTinyUARTComponent::read_array(uint8_t *data, size_t len) {
|
||||
if (!this->check_read_timeout_(len))
|
||||
return false;
|
||||
this->serial_->readBytes(data, len);
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
int LibreTinyUARTComponent::available() { return this->serial_->available(); }
|
||||
void LibreTinyUARTComponent::flush() {
|
||||
ESP_LOGVV(TAG, " Flushing...");
|
||||
this->serial_->flush();
|
||||
}
|
||||
|
||||
void LibreTinyUARTComponent::check_logger_conflict() {
|
||||
#ifdef USE_LOGGER
|
||||
if (this->hardware_idx_ == -1 || logger::global_logger->get_baud_rate() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->serial_ == logger::global_logger->get_hw_serial()) {
|
||||
ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please "
|
||||
"disable logging over the serial port by setting logger->baud_rate to 0.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace uart
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
43
esphome/components/uart/uart_component_libretiny.h
Normal file
43
esphome/components/uart/uart_component_libretiny.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include <vector>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "uart_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uart {
|
||||
|
||||
class LibreTinyUARTComponent : public UARTComponent, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
void write_array(const uint8_t *data, size_t len) override;
|
||||
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
|
||||
int available() override;
|
||||
void flush() override;
|
||||
|
||||
uint16_t get_config();
|
||||
|
||||
HardwareSerial *get_hw_serial() { return this->serial_; }
|
||||
int8_t get_hw_serial_number() { return this->hardware_idx_; }
|
||||
|
||||
protected:
|
||||
void check_logger_conflict() override;
|
||||
|
||||
HardwareSerial *serial_{nullptr};
|
||||
int8_t hardware_idx_{-1};
|
||||
};
|
||||
|
||||
} // namespace uart
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
|
@ -79,13 +79,18 @@ CONFIG_SCHEMA = cv.All(
|
|||
),
|
||||
cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
|
||||
cv.SplitDefault(
|
||||
CONF_OTA, esp8266=True, esp32_arduino=True, esp32_idf=False
|
||||
CONF_OTA,
|
||||
esp8266=True,
|
||||
esp32_arduino=True,
|
||||
esp32_idf=False,
|
||||
bk72xx=True,
|
||||
rtl87xx=True,
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_LOG, default=True): cv.boolean,
|
||||
cv.Optional(CONF_LOCAL): cv.boolean,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on(["esp32", "esp8266"]),
|
||||
cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]),
|
||||
default_url,
|
||||
validate_local,
|
||||
validate_ota,
|
||||
|
|
|
@ -37,4 +37,4 @@ async def to_code(config):
|
|||
cg.add_library("FS", None)
|
||||
cg.add_library("Update", None)
|
||||
# https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
|
||||
cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0")
|
||||
cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.1.0")
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <StreamString.h>
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
#include <Update.h>
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
|
@ -50,7 +50,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin
|
|||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
||||
#endif
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_LIBRETINY)
|
||||
if (Update.isRunning()) {
|
||||
Update.abort();
|
||||
}
|
||||
|
|
|
@ -272,7 +272,12 @@ CONFIG_SCHEMA = cv.All(
|
|||
CONF_REBOOT_TIMEOUT, default="15min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.SplitDefault(
|
||||
CONF_POWER_SAVE_MODE, esp8266="none", esp32="light", rp2040="light"
|
||||
CONF_POWER_SAVE_MODE,
|
||||
esp8266="none",
|
||||
esp32="light",
|
||||
rp2040="light",
|
||||
bk72xx="none",
|
||||
rtl87xx="none",
|
||||
): cv.enum(WIFI_POWER_SAVE_MODES, upper=True),
|
||||
cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WiFiType.h>
|
||||
|
@ -336,6 +340,11 @@ class WiFiComponent : public Component {
|
|||
void wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info);
|
||||
void wifi_scan_done_callback_();
|
||||
#endif
|
||||
|
||||
std::string use_address_;
|
||||
std::vector<WiFiAP> sta_;
|
||||
std::vector<WiFiSTAPriority> sta_priorities_;
|
||||
|
|
467
esphome/components/wifi/wifi_component_libretiny.cpp
Normal file
467
esphome/components/wifi/wifi_component_libretiny.cpp
Normal file
|
@ -0,0 +1,467 @@
|
|||
#include "wifi_component.h"
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include "lwip/ip_addr.h"
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/dns.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace wifi {
|
||||
|
||||
static const char *const TAG = "wifi_lt";
|
||||
|
||||
static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
bool WiFiComponent::wifi_mode_(optional<bool> sta, optional<bool> ap) {
|
||||
uint8_t current_mode = WiFi.getMode();
|
||||
bool current_sta = current_mode & 0b01;
|
||||
bool current_ap = current_mode & 0b10;
|
||||
bool enable_sta = sta.value_or(current_sta);
|
||||
bool enable_ap = ap.value_or(current_ap);
|
||||
if (current_sta == enable_sta && current_ap == enable_ap)
|
||||
return true;
|
||||
|
||||
if (enable_sta && !current_sta) {
|
||||
ESP_LOGV(TAG, "Enabling STA.");
|
||||
} else if (!enable_sta && current_sta) {
|
||||
ESP_LOGV(TAG, "Disabling STA.");
|
||||
}
|
||||
if (enable_ap && !current_ap) {
|
||||
ESP_LOGV(TAG, "Enabling AP.");
|
||||
} else if (!enable_ap && current_ap) {
|
||||
ESP_LOGV(TAG, "Disabling AP.");
|
||||
}
|
||||
|
||||
uint8_t mode = 0;
|
||||
if (enable_sta)
|
||||
mode |= 0b01;
|
||||
if (enable_ap)
|
||||
mode |= 0b10;
|
||||
bool ret = WiFi.mode(static_cast<wifi_mode_t>(mode));
|
||||
|
||||
if (!ret) {
|
||||
ESP_LOGW(TAG, "Setting WiFi mode failed!");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
bool WiFiComponent::wifi_apply_output_power_(float output_power) {
|
||||
int8_t val = static_cast<int8_t>(output_power * 4);
|
||||
return WiFi.setTxPower(val);
|
||||
}
|
||||
bool WiFiComponent::wifi_sta_pre_setup_() {
|
||||
if (!this->wifi_mode_(true, {}))
|
||||
return false;
|
||||
|
||||
WiFi.setAutoReconnect(false);
|
||||
delay(10);
|
||||
return true;
|
||||
}
|
||||
bool WiFiComponent::wifi_apply_power_save_() { return WiFi.setSleep(this->power_save_ != WIFI_POWER_SAVE_NONE); }
|
||||
bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
|
||||
// enable STA
|
||||
if (!this->wifi_mode_(true, {}))
|
||||
return false;
|
||||
|
||||
if (!manual_ip.has_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
WiFi.config(static_cast<uint32_t>(manual_ip->static_ip), static_cast<uint32_t>(manual_ip->gateway),
|
||||
static_cast<uint32_t>(manual_ip->subnet), static_cast<uint32_t>(manual_ip->dns1),
|
||||
static_cast<uint32_t>(manual_ip->dns2));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
network::IPAddress WiFiComponent::wifi_sta_ip() {
|
||||
if (!this->has_sta())
|
||||
return {};
|
||||
return {WiFi.localIP()};
|
||||
}
|
||||
|
||||
bool WiFiComponent::wifi_apply_hostname_() {
|
||||
// setting is done in SYSTEM_EVENT_STA_START callback too
|
||||
WiFi.setHostname(App.get_name().c_str());
|
||||
return true;
|
||||
}
|
||||
bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
// enable STA
|
||||
if (!this->wifi_mode_(true, {}))
|
||||
return false;
|
||||
|
||||
String ssid = WiFi.SSID();
|
||||
if (ssid && strcmp(ssid.c_str(), ap.get_ssid().c_str()) != 0) {
|
||||
WiFi.disconnect();
|
||||
}
|
||||
|
||||
if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->wifi_apply_hostname_();
|
||||
|
||||
s_sta_connecting = true;
|
||||
|
||||
WiFiStatus status = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(),
|
||||
ap.get_channel().has_value() ? *ap.get_channel() : 0,
|
||||
ap.get_bssid().has_value() ? ap.get_bssid()->data() : NULL);
|
||||
if (status != WL_CONNECTED) {
|
||||
ESP_LOGW(TAG, "esp_wifi_connect failed! %d", status);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
const char *get_auth_mode_str(uint8_t mode) {
|
||||
switch (mode) {
|
||||
case WIFI_AUTH_OPEN:
|
||||
return "OPEN";
|
||||
case WIFI_AUTH_WEP:
|
||||
return "WEP";
|
||||
case WIFI_AUTH_WPA_PSK:
|
||||
return "WPA PSK";
|
||||
case WIFI_AUTH_WPA2_PSK:
|
||||
return "WPA2 PSK";
|
||||
case WIFI_AUTH_WPA_WPA2_PSK:
|
||||
return "WPA/WPA2 PSK";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
using esphome_ip4_addr_t = IPAddress;
|
||||
|
||||
std::string format_ip4_addr(const esphome_ip4_addr_t &ip) {
|
||||
char buf[20];
|
||||
uint32_t addr = ip;
|
||||
sprintf(buf, "%u.%u.%u.%u", uint8_t(addr >> 0), uint8_t(addr >> 8), uint8_t(addr >> 16), uint8_t(addr >> 24));
|
||||
return buf;
|
||||
}
|
||||
const char *get_op_mode_str(uint8_t mode) {
|
||||
switch (mode) {
|
||||
case WIFI_OFF:
|
||||
return "OFF";
|
||||
case WIFI_STA:
|
||||
return "STA";
|
||||
case WIFI_AP:
|
||||
return "AP";
|
||||
case WIFI_AP_STA:
|
||||
return "AP+STA";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
const char *get_disconnect_reason_str(uint8_t reason) {
|
||||
switch (reason) {
|
||||
case WIFI_REASON_AUTH_EXPIRE:
|
||||
return "Auth Expired";
|
||||
case WIFI_REASON_AUTH_LEAVE:
|
||||
return "Auth Leave";
|
||||
case WIFI_REASON_ASSOC_EXPIRE:
|
||||
return "Association Expired";
|
||||
case WIFI_REASON_ASSOC_TOOMANY:
|
||||
return "Too Many Associations";
|
||||
case WIFI_REASON_NOT_AUTHED:
|
||||
return "Not Authenticated";
|
||||
case WIFI_REASON_NOT_ASSOCED:
|
||||
return "Not Associated";
|
||||
case WIFI_REASON_ASSOC_LEAVE:
|
||||
return "Association Leave";
|
||||
case WIFI_REASON_ASSOC_NOT_AUTHED:
|
||||
return "Association not Authenticated";
|
||||
case WIFI_REASON_DISASSOC_PWRCAP_BAD:
|
||||
return "Disassociate Power Cap Bad";
|
||||
case WIFI_REASON_DISASSOC_SUPCHAN_BAD:
|
||||
return "Disassociate Supported Channel Bad";
|
||||
case WIFI_REASON_IE_INVALID:
|
||||
return "IE Invalid";
|
||||
case WIFI_REASON_MIC_FAILURE:
|
||||
return "Mic Failure";
|
||||
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
|
||||
return "4-Way Handshake Timeout";
|
||||
case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT:
|
||||
return "Group Key Update Timeout";
|
||||
case WIFI_REASON_IE_IN_4WAY_DIFFERS:
|
||||
return "IE In 4-Way Handshake Differs";
|
||||
case WIFI_REASON_GROUP_CIPHER_INVALID:
|
||||
return "Group Cipher Invalid";
|
||||
case WIFI_REASON_PAIRWISE_CIPHER_INVALID:
|
||||
return "Pairwise Cipher Invalid";
|
||||
case WIFI_REASON_AKMP_INVALID:
|
||||
return "AKMP Invalid";
|
||||
case WIFI_REASON_UNSUPP_RSN_IE_VERSION:
|
||||
return "Unsupported RSN IE version";
|
||||
case WIFI_REASON_INVALID_RSN_IE_CAP:
|
||||
return "Invalid RSN IE Cap";
|
||||
case WIFI_REASON_802_1X_AUTH_FAILED:
|
||||
return "802.1x Authentication Failed";
|
||||
case WIFI_REASON_CIPHER_SUITE_REJECTED:
|
||||
return "Cipher Suite Rejected";
|
||||
case WIFI_REASON_BEACON_TIMEOUT:
|
||||
return "Beacon Timeout";
|
||||
case WIFI_REASON_NO_AP_FOUND:
|
||||
return "AP Not Found";
|
||||
case WIFI_REASON_AUTH_FAIL:
|
||||
return "Authentication Failed";
|
||||
case WIFI_REASON_ASSOC_FAIL:
|
||||
return "Association Failed";
|
||||
case WIFI_REASON_HANDSHAKE_TIMEOUT:
|
||||
return "Handshake Failed";
|
||||
case WIFI_REASON_CONNECTION_FAIL:
|
||||
return "Connection Failed";
|
||||
case WIFI_REASON_UNSPECIFIED:
|
||||
default:
|
||||
return "Unspecified";
|
||||
}
|
||||
}
|
||||
|
||||
#define ESPHOME_EVENT_ID_WIFI_READY ARDUINO_EVENT_WIFI_READY
|
||||
#define ESPHOME_EVENT_ID_WIFI_SCAN_DONE ARDUINO_EVENT_WIFI_SCAN_DONE
|
||||
#define ESPHOME_EVENT_ID_WIFI_STA_START ARDUINO_EVENT_WIFI_STA_START
|
||||
#define ESPHOME_EVENT_ID_WIFI_STA_STOP ARDUINO_EVENT_WIFI_STA_STOP
|
||||
#define ESPHOME_EVENT_ID_WIFI_STA_CONNECTED ARDUINO_EVENT_WIFI_STA_CONNECTED
|
||||
#define ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED ARDUINO_EVENT_WIFI_STA_DISCONNECTED
|
||||
#define ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE
|
||||
#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP ARDUINO_EVENT_WIFI_STA_GOT_IP
|
||||
#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6 ARDUINO_EVENT_WIFI_STA_GOT_IP6
|
||||
#define ESPHOME_EVENT_ID_WIFI_STA_LOST_IP ARDUINO_EVENT_WIFI_STA_LOST_IP
|
||||
#define ESPHOME_EVENT_ID_WIFI_AP_START ARDUINO_EVENT_WIFI_AP_START
|
||||
#define ESPHOME_EVENT_ID_WIFI_AP_STOP ARDUINO_EVENT_WIFI_AP_STOP
|
||||
#define ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED ARDUINO_EVENT_WIFI_AP_STACONNECTED
|
||||
#define ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED ARDUINO_EVENT_WIFI_AP_STADISCONNECTED
|
||||
#define ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED
|
||||
#define ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED
|
||||
#define ESPHOME_EVENT_ID_WIFI_AP_GOT_IP6 ARDUINO_EVENT_WIFI_AP_GOT_IP6
|
||||
using esphome_wifi_event_id_t = arduino_event_id_t;
|
||||
using esphome_wifi_event_info_t = arduino_event_info_t;
|
||||
|
||||
void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) {
|
||||
switch (event) {
|
||||
case ESPHOME_EVENT_ID_WIFI_READY: {
|
||||
ESP_LOGV(TAG, "Event: WiFi ready");
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: {
|
||||
auto it = info.wifi_scan_done;
|
||||
ESP_LOGV(TAG, "Event: WiFi Scan Done status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id);
|
||||
|
||||
this->wifi_scan_done_callback_();
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_START: {
|
||||
ESP_LOGV(TAG, "Event: WiFi STA start");
|
||||
WiFi.setHostname(App.get_name().c_str());
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_STOP: {
|
||||
ESP_LOGV(TAG, "Event: WiFi STA stop");
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: {
|
||||
auto it = info.wifi_sta_connected;
|
||||
char buf[33];
|
||||
memcpy(buf, it.ssid, it.ssid_len);
|
||||
buf[it.ssid_len] = '\0';
|
||||
ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: {
|
||||
auto it = info.wifi_sta_disconnected;
|
||||
char buf[33];
|
||||
memcpy(buf, it.ssid, it.ssid_len);
|
||||
buf[it.ssid_len] = '\0';
|
||||
if (it.reason == WIFI_REASON_NO_AP_FOUND) {
|
||||
ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||
}
|
||||
|
||||
uint8_t reason = it.reason;
|
||||
if (reason == WIFI_REASON_AUTH_EXPIRE || reason == WIFI_REASON_BEACON_TIMEOUT ||
|
||||
reason == WIFI_REASON_NO_AP_FOUND || reason == WIFI_REASON_ASSOC_FAIL ||
|
||||
reason == WIFI_REASON_HANDSHAKE_TIMEOUT) {
|
||||
WiFi.disconnect();
|
||||
this->error_from_callback_ = true;
|
||||
}
|
||||
|
||||
s_sta_connecting = false;
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: {
|
||||
auto it = info.wifi_sta_authmode_change;
|
||||
ESP_LOGV(TAG, "Event: Authmode Change old=%s new=%s", get_auth_mode_str(it.old_mode),
|
||||
get_auth_mode_str(it.new_mode));
|
||||
// Mitigate CVE-2020-12638
|
||||
// https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors
|
||||
if (it.old_mode != WIFI_AUTH_OPEN && it.new_mode == WIFI_AUTH_OPEN) {
|
||||
ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting...");
|
||||
// we can't call retry_connect() from this context, so disconnect immediately
|
||||
// and notify main thread with error_from_callback_
|
||||
WiFi.disconnect();
|
||||
this->error_from_callback_ = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: {
|
||||
// auto it = info.got_ip.ip_info;
|
||||
ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(),
|
||||
format_ip4_addr(WiFi.gatewayIP()).c_str());
|
||||
s_sta_connecting = false;
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: {
|
||||
ESP_LOGV(TAG, "Event: Lost IP");
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_START: {
|
||||
ESP_LOGV(TAG, "Event: WiFi AP start");
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_STOP: {
|
||||
ESP_LOGV(TAG, "Event: WiFi AP stop");
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
|
||||
auto it = info.wifi_sta_connected;
|
||||
auto &mac = it.bssid;
|
||||
ESP_LOGV(TAG, "Event: AP client connected MAC=%s", format_mac_addr(mac).c_str());
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
|
||||
auto it = info.wifi_sta_disconnected;
|
||||
auto &mac = it.bssid;
|
||||
ESP_LOGV(TAG, "Event: AP client disconnected MAC=%s", format_mac_addr(mac).c_str());
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
|
||||
ESP_LOGV(TAG, "Event: AP client assigned IP");
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
|
||||
auto it = info.wifi_ap_probereqrecved;
|
||||
ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void WiFiComponent::wifi_pre_setup_() {
|
||||
auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2);
|
||||
WiFi.onEvent(f);
|
||||
// Make sure WiFi is in clean state before anything starts
|
||||
this->wifi_mode_(false, false);
|
||||
}
|
||||
WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() {
|
||||
auto status = WiFi.status();
|
||||
if (status == WL_CONNECTED) {
|
||||
return WiFiSTAConnectStatus::CONNECTED;
|
||||
} else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) {
|
||||
return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED;
|
||||
} else if (status == WL_NO_SSID_AVAIL) {
|
||||
return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND;
|
||||
} else if (s_sta_connecting) {
|
||||
return WiFiSTAConnectStatus::CONNECTING;
|
||||
}
|
||||
return WiFiSTAConnectStatus::IDLE;
|
||||
}
|
||||
bool WiFiComponent::wifi_scan_start_(bool passive) {
|
||||
// enable STA
|
||||
if (!this->wifi_mode_(true, {}))
|
||||
return false;
|
||||
|
||||
// need to use WiFi because of WiFiScanClass allocations :(
|
||||
int16_t err = WiFi.scanNetworks(true, true, passive, 200);
|
||||
if (err != WIFI_SCAN_RUNNING) {
|
||||
ESP_LOGV(TAG, "WiFi.scanNetworks failed! %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void WiFiComponent::wifi_scan_done_callback_() {
|
||||
this->scan_result_.clear();
|
||||
|
||||
int16_t num = WiFi.scanComplete();
|
||||
if (num < 0)
|
||||
return;
|
||||
|
||||
this->scan_result_.reserve(static_cast<unsigned int>(num));
|
||||
for (int i = 0; i < num; i++) {
|
||||
String ssid = WiFi.SSID(i);
|
||||
wifi_auth_mode_t authmode = WiFi.encryptionType(i);
|
||||
int32_t rssi = WiFi.RSSI(i);
|
||||
uint8_t *bssid = WiFi.BSSID(i);
|
||||
int32_t channel = WiFi.channel(i);
|
||||
|
||||
WiFiScanResult scan({bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]}, std::string(ssid.c_str()),
|
||||
channel, rssi, authmode != WIFI_AUTH_OPEN, ssid.length() == 0);
|
||||
this->scan_result_.push_back(scan);
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
this->scan_done_ = true;
|
||||
}
|
||||
bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||
// enable AP
|
||||
if (!this->wifi_mode_({}, true))
|
||||
return false;
|
||||
|
||||
if (manual_ip.has_value()) {
|
||||
return WiFi.softAPConfig(static_cast<uint32_t>(manual_ip->static_ip), static_cast<uint32_t>(manual_ip->gateway),
|
||||
static_cast<uint32_t>(manual_ip->subnet));
|
||||
} else {
|
||||
return WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0));
|
||||
}
|
||||
}
|
||||
bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||
// enable AP
|
||||
if (!this->wifi_mode_({}, true))
|
||||
return false;
|
||||
|
||||
if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
yield();
|
||||
|
||||
return WiFi.softAP(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(),
|
||||
ap.get_channel().value_or(1), ap.get_hidden());
|
||||
}
|
||||
network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; }
|
||||
bool WiFiComponent::wifi_disconnect_() { return WiFi.disconnect(); }
|
||||
|
||||
bssid_t WiFiComponent::wifi_bssid() {
|
||||
bssid_t bssid{};
|
||||
uint8_t *raw_bssid = WiFi.BSSID();
|
||||
if (raw_bssid != nullptr) {
|
||||
for (size_t i = 0; i < bssid.size(); i++)
|
||||
bssid[i] = raw_bssid[i];
|
||||
}
|
||||
return bssid;
|
||||
}
|
||||
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); }
|
||||
int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); }
|
||||
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; }
|
||||
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; }
|
||||
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; }
|
||||
void WiFiComponent::wifi_loop_() {}
|
||||
|
||||
} // namespace wifi
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
|
@ -1500,6 +1500,8 @@ class SplitDefault(Optional):
|
|||
esp32_arduino=vol.UNDEFINED,
|
||||
esp32_idf=vol.UNDEFINED,
|
||||
rp2040=vol.UNDEFINED,
|
||||
bk72xx=vol.UNDEFINED,
|
||||
rtl87xx=vol.UNDEFINED,
|
||||
host=vol.UNDEFINED,
|
||||
):
|
||||
super().__init__(key)
|
||||
|
@ -1511,6 +1513,8 @@ class SplitDefault(Optional):
|
|||
esp32_idf if esp32 is vol.UNDEFINED else esp32
|
||||
)
|
||||
self._rp2040_default = vol.default_factory(rp2040)
|
||||
self._bk72xx_default = vol.default_factory(bk72xx)
|
||||
self._rtl87xx_default = vol.default_factory(rtl87xx)
|
||||
self._host_default = vol.default_factory(host)
|
||||
|
||||
@property
|
||||
|
@ -1523,6 +1527,10 @@ class SplitDefault(Optional):
|
|||
return self._esp32_idf_default
|
||||
if CORE.is_rp2040:
|
||||
return self._rp2040_default
|
||||
if CORE.is_bk72xx:
|
||||
return self._bk72xx_default
|
||||
if CORE.is_rtl87xx:
|
||||
return self._rtl87xx_default
|
||||
if CORE.is_host:
|
||||
return self._host_default
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -11,8 +11,19 @@ PLATFORM_ESP32 = "esp32"
|
|||
PLATFORM_ESP8266 = "esp8266"
|
||||
PLATFORM_RP2040 = "rp2040"
|
||||
PLATFORM_HOST = "host"
|
||||
PLATFORM_BK72XX = "bk72xx"
|
||||
PLATFORM_RTL87XX = "rtl87xx"
|
||||
PLATFORM_LIBRETINY_OLDSTYLE = "libretiny"
|
||||
|
||||
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_HOST]
|
||||
TARGET_PLATFORMS = [
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_HOST,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_RTL87XX,
|
||||
PLATFORM_LIBRETINY_OLDSTYLE,
|
||||
]
|
||||
|
||||
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
|
||||
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
|
||||
|
@ -37,6 +48,7 @@ CONF_ADVANCED = "advanced"
|
|||
CONF_AFTER = "after"
|
||||
CONF_ALPHA = "alpha"
|
||||
CONF_ALTITUDE = "altitude"
|
||||
CONF_ANALOG = "analog"
|
||||
CONF_AND = "and"
|
||||
CONF_AP = "ap"
|
||||
CONF_APPARENT_POWER = "apparent_power"
|
||||
|
@ -835,6 +847,7 @@ ICON_BRIEFCASE_DOWNLOAD = "mdi:briefcase-download"
|
|||
ICON_BRIGHTNESS_5 = "mdi:brightness-5"
|
||||
ICON_BRIGHTNESS_6 = "mdi:brightness-6"
|
||||
ICON_BUG = "mdi:bug"
|
||||
ICON_CELLPHONE_ARROW_DOWN = "mdi:cellphone-arrow-down"
|
||||
ICON_CHECK_CIRCLE_OUTLINE = "mdi:check-circle-outline"
|
||||
ICON_CHEMICAL_WEAPON = "mdi:chemical-weapon"
|
||||
ICON_CHIP = "mdi:chip"
|
||||
|
|
|
@ -584,6 +584,8 @@ class EsphomeCore:
|
|||
|
||||
@property
|
||||
def firmware_bin(self):
|
||||
if self.is_libretiny:
|
||||
return self.relative_pioenvs_path(self.name, "firmware.uf2")
|
||||
return self.relative_pioenvs_path(self.name, "firmware.bin")
|
||||
|
||||
@property
|
||||
|
@ -602,6 +604,18 @@ class EsphomeCore:
|
|||
def is_rp2040(self):
|
||||
return self.target_platform == "rp2040"
|
||||
|
||||
@property
|
||||
def is_bk72xx(self):
|
||||
return self.target_platform == "bk72xx"
|
||||
|
||||
@property
|
||||
def is_rtl87xx(self):
|
||||
return self.target_platform == "rtl87xx"
|
||||
|
||||
@property
|
||||
def is_libretiny(self):
|
||||
return self.is_bk72xx or self.is_rtl87xx
|
||||
|
||||
@property
|
||||
def is_host(self):
|
||||
return self.target_platform == "host"
|
||||
|
|
|
@ -380,7 +380,7 @@ async def to_code(config):
|
|||
cg.add_build_flag("-Wno-unused-but-set-variable")
|
||||
cg.add_build_flag("-Wno-sign-compare")
|
||||
|
||||
if CORE.using_arduino:
|
||||
if CORE.using_arduino and not CORE.is_bk72xx:
|
||||
CORE.add_job(add_arduino_global_workaround)
|
||||
|
||||
if config[CONF_INCLUDES]:
|
||||
|
|
|
@ -95,6 +95,10 @@
|
|||
#define USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
#define USE_SOCKET_IMPL_LWIP_TCP
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
#define USE_SOCKET_IMPL_LWIP_SOCKETS
|
||||
#endif
|
||||
|
||||
// Dummy firmware payload for shelly_dimmer
|
||||
#define USE_SHD_FIRMWARE_MAJOR_VERSION 56
|
||||
#define USE_SHD_FIRMWARE_MINOR_VERSION 5
|
||||
|
|
|
@ -43,6 +43,10 @@
|
|||
#include "esp_efuse_table.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
#include <WiFi.h> // for macAddress()
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
static const char *const TAG = "helpers";
|
||||
|
@ -190,6 +194,8 @@ uint32_t random_uint32() {
|
|||
result |= rosc_hw->randombit;
|
||||
}
|
||||
return result;
|
||||
#elif defined(USE_LIBRETINY)
|
||||
return rand();
|
||||
#elif defined(USE_HOST)
|
||||
std::random_device dev;
|
||||
std::mt19937 rng(dev());
|
||||
|
@ -216,6 +222,9 @@ bool random_bytes(uint8_t *data, size_t len) {
|
|||
*data++ = result;
|
||||
}
|
||||
return true;
|
||||
#elif defined(USE_LIBRETINY)
|
||||
lt_rand_bytes(data, len);
|
||||
return true;
|
||||
#elif defined(USE_HOST)
|
||||
FILE *fp = fopen("/dev/urandom", "r");
|
||||
if (fp == nullptr) {
|
||||
|
@ -503,7 +512,7 @@ Mutex::Mutex() {}
|
|||
void Mutex::lock() {}
|
||||
bool Mutex::try_lock() { return true; }
|
||||
void Mutex::unlock() {}
|
||||
#elif defined(USE_ESP32)
|
||||
#elif defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); }
|
||||
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
|
||||
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
|
||||
|
@ -513,7 +522,7 @@ void Mutex::unlock() { xSemaphoreGive(this->handle_); }
|
|||
#if defined(USE_ESP8266)
|
||||
IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); }
|
||||
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); }
|
||||
#elif defined(USE_ESP32)
|
||||
#elif defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
// only affects the executing core
|
||||
// so should not be used as a mutex lock, only to get accurate timing
|
||||
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
|
||||
|
@ -555,6 +564,8 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame
|
|||
wifi_get_macaddr(STATION_IF, mac);
|
||||
#elif defined(USE_RP2040) && defined(USE_WIFI)
|
||||
WiFi.macAddress(mac);
|
||||
#elif defined(USE_LIBRETINY)
|
||||
WiFi.macAddress(mac);
|
||||
#endif
|
||||
}
|
||||
std::string get_mac_address() {
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
#if defined(USE_ESP32)
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#elif defined(USE_LIBRETINY)
|
||||
#include <FreeRTOS.h>
|
||||
#include <semphr.h>
|
||||
#endif
|
||||
|
||||
#define HOT __attribute__((hot))
|
||||
|
@ -543,7 +546,7 @@ class Mutex {
|
|||
Mutex &operator=(const Mutex &) = delete;
|
||||
|
||||
private:
|
||||
#if defined(USE_ESP32)
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
SemaphoreHandle_t handle_;
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#include <esp32-hal-log.h>
|
||||
#endif
|
||||
#ifdef USE_LIBRETINY
|
||||
#include <lt_logger.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
|
|
|
@ -543,6 +543,7 @@ class DownloadListRequestHandler(BaseHandler):
|
|||
from esphome.components.esp32 import get_download_types as esp32_types
|
||||
from esphome.components.esp8266 import get_download_types as esp8266_types
|
||||
from esphome.components.rp2040 import get_download_types as rp2040_types
|
||||
from esphome.components.libretiny import get_download_types as libretiny_types
|
||||
|
||||
downloads = []
|
||||
platform = storage_json.target_platform.lower()
|
||||
|
@ -552,6 +553,10 @@ class DownloadListRequestHandler(BaseHandler):
|
|||
downloads = esp8266_types(storage_json)
|
||||
elif platform == const.PLATFORM_ESP32:
|
||||
downloads = esp32_types(storage_json)
|
||||
elif platform == const.PLATFORM_BK72XX:
|
||||
downloads = libretiny_types(storage_json)
|
||||
elif platform == const.PLATFORM_RTL87XX:
|
||||
downloads = libretiny_types(storage_json)
|
||||
else:
|
||||
self.send_error(418)
|
||||
return
|
||||
|
@ -826,11 +831,15 @@ class BoardsRequestHandler(BaseHandler):
|
|||
from esphome.components.esp32.boards import BOARDS as ESP32_BOARDS
|
||||
from esphome.components.esp8266.boards import BOARDS as ESP8266_BOARDS
|
||||
from esphome.components.rp2040.boards import BOARDS as RP2040_BOARDS
|
||||
from esphome.components.bk72xx.boards import BOARDS as BK72XX_BOARDS
|
||||
from esphome.components.rtl87xx.boards import BOARDS as RTL87XX_BOARDS
|
||||
|
||||
platform_to_boards = {
|
||||
"esp32": ESP32_BOARDS,
|
||||
"esp8266": ESP8266_BOARDS,
|
||||
"rp2040": RP2040_BOARDS,
|
||||
"bk72xx": BK72XX_BOARDS,
|
||||
"rtl87xx": RTL87XX_BOARDS,
|
||||
}
|
||||
# filter all ESP32 variants by requested platform
|
||||
if platform.startswith("esp32"):
|
||||
|
|
|
@ -203,6 +203,11 @@ class _Schema(vol.Schema):
|
|||
self._extra_schemas.append(validator)
|
||||
return self
|
||||
|
||||
def prepend_extra(self, validator):
|
||||
validator = _Schema(validator)
|
||||
self._extra_schemas.insert(0, validator)
|
||||
return self
|
||||
|
||||
@schema_extractor_extended
|
||||
def extend(self, *schemas, **kwargs):
|
||||
extra = kwargs.pop("extra", None)
|
||||
|
|
|
@ -93,12 +93,24 @@ rp2040:
|
|||
platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git
|
||||
"""
|
||||
|
||||
BK72XX_CONFIG = """
|
||||
bk72xx:
|
||||
board: {board}
|
||||
"""
|
||||
|
||||
RTL87XX_CONFIG = """
|
||||
rtl87xx:
|
||||
board: {board}
|
||||
"""
|
||||
|
||||
HARDWARE_BASE_CONFIGS = {
|
||||
"ESP8266": ESP8266_CONFIG,
|
||||
"ESP32": ESP32_CONFIG,
|
||||
"ESP32S2": ESP32S2_CONFIG,
|
||||
"ESP32C3": ESP32C3_CONFIG,
|
||||
"RP2040": RP2040_CONFIG,
|
||||
"BK72XX": BK72XX_CONFIG,
|
||||
"RTL87XX": RTL87XX_CONFIG,
|
||||
}
|
||||
|
||||
|
||||
|
@ -156,7 +168,7 @@ def wizard_file(**kwargs):
|
|||
"""
|
||||
|
||||
# pylint: disable=consider-using-f-string
|
||||
if kwargs["platform"] in ["ESP8266", "ESP32"]:
|
||||
if kwargs["platform"] in ["ESP8266", "ESP32", "BK72XX", "RTL87XX"]:
|
||||
config += """
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
|
@ -182,7 +194,10 @@ captive_portal:
|
|||
|
||||
def wizard_write(path, **kwargs):
|
||||
from esphome.components.esp8266 import boards as esp8266_boards
|
||||
from esphome.components.esp32 import boards as esp32_boards
|
||||
from esphome.components.rp2040 import boards as rp2040_boards
|
||||
from esphome.components.bk72xx import boards as bk72xx_boards
|
||||
from esphome.components.rtl87xx import boards as rtl87xx_boards
|
||||
|
||||
name = kwargs["name"]
|
||||
board = kwargs["board"]
|
||||
|
@ -192,12 +207,19 @@ def wizard_write(path, **kwargs):
|
|||
kwargs[key] = sanitize_double_quotes(kwargs[key])
|
||||
|
||||
if "platform" not in kwargs:
|
||||
if board in esp8266_boards.ESP8266_BOARD_PINS:
|
||||
if board in esp8266_boards.BOARDS:
|
||||
platform = "ESP8266"
|
||||
elif board in rp2040_boards.RP2040_BOARD_PINS:
|
||||
platform = "RP2040"
|
||||
else:
|
||||
elif board in esp32_boards.BOARDS:
|
||||
platform = "ESP32"
|
||||
elif board in rp2040_boards.BOARDS:
|
||||
platform = "RP2040"
|
||||
elif board in bk72xx_boards.BOARDS:
|
||||
platform = "BK72XX"
|
||||
elif board in rtl87xx_boards.BOARDS:
|
||||
platform = "RTL87XX"
|
||||
else:
|
||||
safe_print(color(Fore.RED, f'The board "{board}" is unknown.'))
|
||||
return False
|
||||
kwargs["platform"] = platform
|
||||
hardware = kwargs["platform"]
|
||||
|
||||
|
@ -206,6 +228,8 @@ def wizard_write(path, **kwargs):
|
|||
storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path))
|
||||
storage.save(storage_path)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if get_bool_env(ENV_QUICKWIZARD):
|
||||
|
||||
|
@ -243,6 +267,8 @@ def strip_accents(value):
|
|||
def wizard(path):
|
||||
from esphome.components.esp32 import boards as esp32_boards
|
||||
from esphome.components.esp8266 import boards as esp8266_boards
|
||||
from esphome.components.bk72xx import boards as bk72xx_boards
|
||||
from esphome.components.rtl87xx import boards as rtl87xx_boards
|
||||
|
||||
if not path.endswith(".yaml") and not path.endswith(".yml"):
|
||||
safe_print(
|
||||
|
@ -262,7 +288,7 @@ def wizard(path):
|
|||
sleep(2.0)
|
||||
safe_print(
|
||||
"In 4 steps I'm going to guide you through creating a basic "
|
||||
"configuration file for your custom ESP8266/ESP32 firmware. Yay!"
|
||||
"configuration file for your custom firmware. Yay!"
|
||||
)
|
||||
sleep(3.0)
|
||||
safe_print()
|
||||
|
@ -305,16 +331,18 @@ def wizard(path):
|
|||
"Now I'd like to know what microcontroller you're using so that I can compile "
|
||||
"firmwares for it."
|
||||
)
|
||||
|
||||
wizard_platforms = ["ESP32", "ESP8266", "BK72XX", "RTL87XX"]
|
||||
safe_print(
|
||||
f"Are you using an {color(Fore.GREEN, 'ESP32')} or {color(Fore.GREEN, 'ESP8266')} platform? (Choose ESP8266 for Sonoff devices)"
|
||||
"Please choose one of the supported microcontrollers "
|
||||
"(Use ESP8266 for Sonoff devices)."
|
||||
)
|
||||
while True:
|
||||
sleep(0.5)
|
||||
safe_print()
|
||||
safe_print("Please enter either ESP32 or ESP8266.")
|
||||
platform = input(color(Fore.BOLD_WHITE, "(ESP32/ESP8266): "))
|
||||
platform = input(color(Fore.BOLD_WHITE, f"({'/'.join(wizard_platforms)}): "))
|
||||
try:
|
||||
platform = vol.All(vol.Upper, vol.Any("ESP32", "ESP8266"))(platform)
|
||||
platform = vol.All(vol.Upper, vol.Any(*wizard_platforms))(platform.upper())
|
||||
break
|
||||
except vol.Invalid:
|
||||
safe_print(
|
||||
|
@ -328,10 +356,14 @@ def wizard(path):
|
|||
board_link = (
|
||||
"http://docs.platformio.org/en/latest/platforms/espressif32.html#boards"
|
||||
)
|
||||
else:
|
||||
elif platform == "ESP8266":
|
||||
board_link = (
|
||||
"http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards"
|
||||
)
|
||||
elif platform in ["BK72XX", "RTL87XX"]:
|
||||
board_link = "https://docs.libretiny.eu/docs/status/supported/"
|
||||
else:
|
||||
raise NotImplementedError("Unknown platform!")
|
||||
|
||||
safe_print(f"Next, I need to know what {color(Fore.GREEN, 'board')} you're using.")
|
||||
sleep(0.5)
|
||||
|
@ -342,11 +374,24 @@ def wizard(path):
|
|||
# Don't sleep because user needs to copy link
|
||||
if platform == "ESP32":
|
||||
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcu-32s')}\".")
|
||||
boards = list(esp32_boards.ESP32_BOARD_PINS.keys())
|
||||
else:
|
||||
boards_list = esp32_boards.BOARDS.items()
|
||||
elif platform == "ESP8266":
|
||||
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcuv2')}\".")
|
||||
boards = list(esp8266_boards.ESP8266_BOARD_PINS.keys())
|
||||
safe_print(f"Options: {', '.join(sorted(boards))}")
|
||||
boards_list = esp8266_boards.BOARDS.items()
|
||||
elif platform == "BK72XX":
|
||||
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'cb2s')}\".")
|
||||
boards_list = bk72xx_boards.BOARDS.items()
|
||||
elif platform == "RTL87XX":
|
||||
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'wr3')}\".")
|
||||
boards_list = rtl87xx_boards.BOARDS.items()
|
||||
else:
|
||||
raise NotImplementedError("Unknown platform!")
|
||||
|
||||
boards = []
|
||||
safe_print("Options:")
|
||||
for board_id, board_data in boards_list:
|
||||
safe_print(f" - {board_id} - {board_data['name']}")
|
||||
boards.append(board_id)
|
||||
|
||||
while True:
|
||||
board = input(color(Fore.BOLD_WHITE, "(board): "))
|
||||
|
@ -420,7 +465,7 @@ def wizard(path):
|
|||
safe_print("Press ENTER for no password")
|
||||
password = input(color(Fore.BOLD_WHITE, "(password): "))
|
||||
|
||||
wizard_write(
|
||||
if not wizard_write(
|
||||
path=path,
|
||||
name=name,
|
||||
platform=platform,
|
||||
|
@ -428,7 +473,8 @@ def wizard(path):
|
|||
ssid=ssid,
|
||||
psk=psk,
|
||||
password=password,
|
||||
)
|
||||
):
|
||||
return 1
|
||||
|
||||
safe_print()
|
||||
safe_print(
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
; It's *not* used during runtime.
|
||||
|
||||
[platformio]
|
||||
default_envs = esp8266-arduino, esp32-arduino, esp32-idf
|
||||
default_envs = esp8266-arduino, esp32-arduino, esp32-idf, bk72xx-arduino
|
||||
; Ideally, we want src_dir to be the root directory of the repository, to mimic the runtime build
|
||||
; environment as best as possible. Unfortunately, the ESP-IDF toolchain really doesn't like this
|
||||
; being the root directory. Instead, set esphome/ as the source directory, all our sources are in
|
||||
|
@ -167,6 +167,16 @@ build_flags =
|
|||
-DUSE_RP2040
|
||||
-DUSE_RP2040_FRAMEWORK_ARDUINO
|
||||
|
||||
; This are common settings for the LibreTiny (all variants) using Arduino.
|
||||
[common:libretiny-arduino]
|
||||
extends = common:arduino
|
||||
platform = libretiny
|
||||
framework = arduino
|
||||
build_flags =
|
||||
${common:arduino.build_flags}
|
||||
-DUSE_LIBRETINY
|
||||
build_src_flags = -include Arduino.h
|
||||
|
||||
; All the actual environments are defined below.
|
||||
|
||||
;;;;;;;; ESP8266 ;;;;;;;;
|
||||
|
@ -339,6 +349,35 @@ build_flags =
|
|||
${common:rp2040-arduino.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
|
||||
;;;;;;;; LibreTiny ;;;;;;;;
|
||||
|
||||
[env:bk72xx-arduino]
|
||||
extends = common:libretiny-arduino
|
||||
board = generic-bk7231n-qfn32-tuya
|
||||
build_flags =
|
||||
${common:libretiny-arduino.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_BK72XX
|
||||
-DUSE_LIBRETINY_VARIANT_BK7231N
|
||||
|
||||
[env:rtl87xxb-arduino]
|
||||
extends = common:libretiny-arduino
|
||||
board = generic-rtl8710bn-2mb-788k
|
||||
build_flags =
|
||||
${common:libretiny-arduino.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_RTL87XX
|
||||
-DUSE_LIBRETINY_VARIANT_RTL8710B
|
||||
|
||||
[env:rtl87xxc-arduino]
|
||||
extends = common:libretiny-arduino
|
||||
board = generic-rtl8720cf-2mb-992k
|
||||
build_flags =
|
||||
${common:libretiny-arduino.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_RTL87XX
|
||||
-DUSE_LIBRETINY_VARIANT_RTL8720C
|
||||
|
||||
[env:host]
|
||||
extends = common
|
||||
platform = platformio/native
|
||||
|
|
|
@ -512,7 +512,10 @@ def relative_py_search_text(fname, content):
|
|||
@lint_content_find_check(
|
||||
relative_py_search_text,
|
||||
include=["esphome/components/*.py"],
|
||||
exclude=["esphome/components/web_server/__init__.py"],
|
||||
exclude=[
|
||||
"esphome/components/libretiny/generate_components.py",
|
||||
"esphome/components/web_server/__init__.py",
|
||||
],
|
||||
)
|
||||
def lint_relative_py_import(fname):
|
||||
return (
|
||||
|
@ -536,6 +539,7 @@ def lint_relative_py_import(fname):
|
|||
"esphome/components/esp32/core.cpp",
|
||||
"esphome/components/esp8266/core.cpp",
|
||||
"esphome/components/rp2040/core.cpp",
|
||||
"esphome/components/libretiny/core.cpp",
|
||||
"esphome/components/host/core.cpp",
|
||||
],
|
||||
)
|
||||
|
|
28
tests/test9.1.yaml
Normal file
28
tests/test9.1.yaml
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Tests for rtl87xx boards using LibreTiny
|
||||
---
|
||||
wifi:
|
||||
ssid: "ssid"
|
||||
|
||||
rtl87xx:
|
||||
board: generic-rtl8710bn-2mb-788k
|
||||
|
||||
esphome:
|
||||
name: rtl87xx-test
|
||||
|
||||
logger:
|
||||
|
||||
ota:
|
||||
|
||||
captive_portal:
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: Home Button
|
||||
pin: GPIO11
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: adc_sensor
|
||||
name: ADC
|
||||
pin: PA19
|
||||
update_interval: 1s
|
28
tests/test9.yaml
Normal file
28
tests/test9.yaml
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Tests for bk7xx boards using LibreTiny
|
||||
---
|
||||
wifi:
|
||||
ssid: "ssid"
|
||||
|
||||
bk72xx:
|
||||
board: cb2s
|
||||
|
||||
esphome:
|
||||
name: bk72xx-test
|
||||
|
||||
logger:
|
||||
|
||||
ota:
|
||||
|
||||
captive_portal:
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: Home Button
|
||||
pin: GPIO24
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: adc_sensor
|
||||
name: ADC
|
||||
pin: GPIO23
|
||||
update_interval: 1s
|
|
@ -3,6 +3,9 @@
|
|||
import esphome.wizard as wz
|
||||
import pytest
|
||||
from esphome.components.esp8266.boards import ESP8266_BOARD_PINS
|
||||
from esphome.components.esp32.boards import ESP32_BOARD_PINS
|
||||
from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS
|
||||
from esphome.components.rtl87xx.boards import RTL87XX_BOARD_PINS
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
|
@ -140,11 +143,11 @@ def test_wizard_write_defaults_platform_from_board_esp32(
|
|||
default_config, tmp_path, monkeypatch
|
||||
):
|
||||
"""
|
||||
If the platform is not explicitly set, use "ESP32" if the board is not one of the ESP8266 boards
|
||||
If the platform is not explicitly set, use "ESP32" if the board is one of the ESP32 boards
|
||||
"""
|
||||
# Given
|
||||
del default_config["platform"]
|
||||
default_config["board"] = "foo"
|
||||
default_config["board"] = [*ESP32_BOARD_PINS][0]
|
||||
|
||||
monkeypatch.setattr(wz, "write_file", MagicMock())
|
||||
|
||||
|
@ -156,6 +159,46 @@ def test_wizard_write_defaults_platform_from_board_esp32(
|
|||
assert "esp32:" in generated_config
|
||||
|
||||
|
||||
def test_wizard_write_defaults_platform_from_board_bk72xx(
|
||||
default_config, tmp_path, monkeypatch
|
||||
):
|
||||
"""
|
||||
If the platform is not explicitly set, use "BK72XX" if the board is one of BK72XX boards
|
||||
"""
|
||||
# Given
|
||||
del default_config["platform"]
|
||||
default_config["board"] = [*BK72XX_BOARD_PINS][0]
|
||||
|
||||
monkeypatch.setattr(wz, "write_file", MagicMock())
|
||||
|
||||
# When
|
||||
wz.wizard_write(tmp_path, **default_config)
|
||||
|
||||
# Then
|
||||
generated_config = wz.write_file.call_args.args[1]
|
||||
assert "bk72xx:" in generated_config
|
||||
|
||||
|
||||
def test_wizard_write_defaults_platform_from_board_rtl87xx(
|
||||
default_config, tmp_path, monkeypatch
|
||||
):
|
||||
"""
|
||||
If the platform is not explicitly set, use "RTL87XX" if the board is one of RTL87XX boards
|
||||
"""
|
||||
# Given
|
||||
del default_config["platform"]
|
||||
default_config["board"] = [*RTL87XX_BOARD_PINS][0]
|
||||
|
||||
monkeypatch.setattr(wz, "write_file", MagicMock())
|
||||
|
||||
# When
|
||||
wz.wizard_write(tmp_path, **default_config)
|
||||
|
||||
# Then
|
||||
generated_config = wz.write_file.call_args.args[1]
|
||||
assert "rtl87xx:" in generated_config
|
||||
|
||||
|
||||
def test_safe_print_step_prints_step_number_and_description(monkeypatch):
|
||||
"""
|
||||
The safe_print_step function prints the step number and the passed description
|
||||
|
@ -186,7 +229,7 @@ def test_default_input_uses_default_if_no_input_supplied(monkeypatch):
|
|||
"""
|
||||
|
||||
# Given
|
||||
monkeypatch.setattr("builtins.input", lambda _: "")
|
||||
monkeypatch.setattr("builtins.input", lambda _=None: "")
|
||||
default_string = "foobar"
|
||||
|
||||
# When
|
||||
|
@ -203,7 +246,7 @@ def test_default_input_uses_user_supplied_value(monkeypatch):
|
|||
|
||||
# Given
|
||||
user_input = "A value"
|
||||
monkeypatch.setattr("builtins.input", lambda _: user_input)
|
||||
monkeypatch.setattr("builtins.input", lambda _=None: user_input)
|
||||
default_string = "foobar"
|
||||
|
||||
# When
|
||||
|
|
Loading…
Reference in a new issue