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