mirror of
https://github.com/esphome/esphome.git
synced 2026-05-22 01:42:49 +08: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:
committed by
GitHub
parent
22c0b0abaa
commit
a9630ac847
@@ -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
|
||||
|
||||
+14
-7
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
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));
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny {} // namespace libretiny
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny {
|
||||
|
||||
void setup_preferences();
|
||||
|
||||
} // namespace libretiny
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
||||
@@ -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))
|
||||
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@kuba2k2"]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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});
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
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")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user