Platform 2025.12.31

This commit is contained in:
Jason2866 2025-12-24 15:06:28 +01:00 committed by GitHub
parent 30cef9c501
commit 54a152f440
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 118 additions and 36 deletions

View File

@ -7,7 +7,7 @@
- [ ] Only relevant files were touched
- [ ] Only one feature/fix was added per PR and the code change compiles without warnings
- [ ] The code change is tested and works with Tasmota core ESP8266 V.2.7.8
- [ ] The code change is tested and works with Tasmota core ESP32 V.3.1.7
- [ ] The code change is tested and works with Tasmota core ESP32 V.3.1.8
- [ ] I accept the [CLA](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md#contributor-license-agreement-cla).
_NOTE: The code change must pass CI tests. **Your PR cannot be merged unless tests pass**_

View File

@ -1,18 +1,17 @@
# Written by Maximilian Gerhardt <maximilian.gerhardt@rub.de>
# 29th December 2020
# and Christian Baars, Johann Obermeier
# 2023 / 2024
# 2023 - 2025
# License: Apache
# Expanded from functionality provided by PlatformIO's espressif32 and espressif8266 platforms, credited below.
# This script provides functions to download the filesystem (LittleFS) from a running ESP32 / ESP8266
# over the serial bootloader using esptool.py, and mklittlefs for extracting.
# over the serial bootloader using esptool.py, and littlefs-python for extracting.
# run by either using the VSCode task "Custom" -> "Download Filesystem"
# or by doing 'pio run -t downloadfs' (with optional '-e <environment>') from the commandline.
# output will be saved, by default, in the "unpacked_fs" of the project.
# this folder can be changed by writing 'custom_unpack_dir = some_other_dir' in the corresponding platformio.ini
# environment.
import re
import sys
from os.path import isfile, join
from enum import Enum
import os
@ -20,7 +19,9 @@ import tasmotapiolib
import subprocess
import shutil
import json
from pathlib import Path
from colorama import Fore, Back, Style
from littlefs import LittleFS
from platformio.compat import IS_WINDOWS
from platformio.project.config import ProjectConfig
@ -42,19 +43,12 @@ class FSInfo:
self.block_size = block_size
def __repr__(self):
return f"FS type {self.fs_type} Start {hex(self.start)} Len {self.length} Page size {self.page_size} Block size {self.block_size}"
# extract command supposed to be implemented by subclasses
def get_extract_cmd(self, input_file, output_dir):
raise NotImplementedError()
class FS_Info(FSInfo):
def __init__(self, start, length, page_size, block_size):
self.tool = env["MKFSTOOL"]
self.tool = os.path.join(ProjectConfig.get_instance().get("platformio", "packages_dir"), "tool-mklittlefs", self.tool)
super().__init__(FSType.LITTLEFS, start, length, page_size, block_size)
def __repr__(self):
return f"{self.fs_type} Start {hex(self.start)} Len {hex(self.length)} Page size {hex(self.page_size)} Block size {hex(self.block_size)}"
def get_extract_cmd(self, input_file, output_dir):
return f'"{self.tool}" -b {self.block_size} -s {self.length} -p {self.page_size} --unpack "{output_dir}" "{input_file}"'
def _parse_size(value):
if isinstance(value, int):
@ -150,15 +144,11 @@ switch_off_ldf()
## Script interface functions
def parse_partition_table(content):
entries = [e for e in content.split(b'\xaaP') if len(e) > 0]
#print("Partition data:")
for entry in entries:
type = entry[1]
if type in [0x82,0x83]: # SPIFFS or LITTLEFS
offset = int.from_bytes(entry[2:5], byteorder='little', signed=False)
size = int.from_bytes(entry[6:9], byteorder='little', signed=False)
#print("type:",hex(type))
#print("address:",hex(offset))
#print("size:",hex(size))
offset = int.from_bytes(entry[2:6], byteorder='little', signed=False)
size = int.from_bytes(entry[6:10], byteorder='little', signed=False)
env["FS_START"] = offset
env["FS_SIZE"] = size
env["FS_PAGE"] = int("0x100", 16)
@ -266,20 +256,67 @@ def unpack_fs(fs_info: FSInfo, downloaded_file: str):
if not os.path.exists(unpack_dir):
os.makedirs(unpack_dir)
cmd = fs_info.get_extract_cmd(downloaded_file, unpack_dir)
print("Unpack files from filesystem image")
print()
try:
returncode = subprocess.call(cmd, shell=True)
# Read the downloaded filesystem image
with open(downloaded_file, 'rb') as f:
fs_data = f.read()
# Calculate block count
block_count = fs_info.length // fs_info.block_size
# Create LittleFS instance and mount the image
fs = LittleFS(
block_size=fs_info.block_size,
block_count=block_count,
mount=False
)
fs.context.buffer = bytearray(fs_data)
fs.mount()
# Extract all files
unpack_path = Path(unpack_dir)
for root, dirs, files in fs.walk("/"):
if not root.endswith("/"):
root += "/"
# Create directories
for dir_name in dirs:
src_path = root + dir_name
dst_path = unpack_path / src_path[1:] # Remove leading '/'
dst_path.mkdir(parents=True, exist_ok=True)
# Extract files
for file_name in files:
src_path = root + file_name
dst_path = unpack_path / src_path[1:] # Remove leading '/'
dst_path.parent.mkdir(parents=True, exist_ok=True)
with fs.open(src_path, "rb") as src:
dst_path.write_bytes(src.read())
fs.unmount()
return (True, unpack_dir)
except subprocess.CalledProcessError as exc:
print("Unpacking filesystem failed with " + str(exc))
except Exception as exc:
print("Unpacking filesystem with littlefs-python failed with " + str(exc))
return (False, "")
def display_fs(extracted_dir):
# extract command already nicely lists all extracted files.
# no need to display that ourselves. just display a summary
file_count = sum([len(files) for r, d, files in os.walk(extracted_dir)])
print("Extracted " + str(file_count) + " file(s) from filesystem.")
# List all extracted files
file_count = 0
print(Fore.GREEN + "Extracted files from filesystem image:")
print()
for root, dirs, files in os.walk(extracted_dir):
# Display directories
for dir_name in dirs:
dir_path = os.path.join(root, dir_name)
rel_path = os.path.relpath(dir_path, extracted_dir)
print(f" [DIR] {rel_path}/")
# Display files
for file_name in files:
file_path = os.path.join(root, file_name)
rel_path = os.path.relpath(file_path, extracted_dir)
file_size = os.path.getsize(file_path)
print(f" [FILE] {rel_path} ({file_size} bytes)")
file_count += 1
print(f"\nExtracted {file_count} file(s) from filesystem.")
def command_download_fs(*args, **kwargs):
info = get_fs_type_start_and_length()

View File

@ -24,10 +24,12 @@ from genericpath import exists
import os
from os.path import join, getsize
import csv
from littlefs import LittleFS
import requests
import shutil
import subprocess
import codecs
from pathlib import Path
from colorama import Fore
from SCons.Script import COMMAND_LINE_TARGETS
@ -134,7 +136,7 @@ def esp32_build_filesystem(fs_size):
os.makedirs(filesystem_dir)
if num_entries > 1:
print()
print(Fore.GREEN + "Will create filesystem with the following files:")
print(Fore.GREEN + "Will create filesystem with the following file(s):")
print()
for file in files:
if "no_files" in file:
@ -146,21 +148,66 @@ def esp32_build_filesystem(fs_size):
if len(file.split(" ")) > 1:
target = os.path.normpath(join(filesystem_dir, file.split(" ")[1]))
print("Renaming",(file.split(os.path.sep)[-1]).split(" ")[0],"to",file.split(" ")[1])
else:
print(file.split(os.path.sep)[-1])
open(target, "wb").write(response.content)
else:
print(Fore.RED + "Failed to download: ",file)
continue
if os.path.isdir(file):
print(f"{file}/ (directory)")
shutil.copytree(file, filesystem_dir, dirs_exist_ok=True)
else:
print(file)
shutil.copy(file, filesystem_dir)
if not os.listdir(filesystem_dir):
#print("No files added -> will NOT create littlefs.bin and NOT overwrite fs partition!")
return False
tool = env.subst(env["MKFSTOOL"])
cmd = (tool,"-c",filesystem_dir,"-s",fs_size,join(env.subst("$BUILD_DIR"),"littlefs.bin"))
returncode = subprocess.call(cmd, shell=False)
# print(returncode)
# Use littlefs-python
output_file = join(env.subst("$BUILD_DIR"), "littlefs.bin")
# Parse fs_size (can be hex string like "0x2f0000")
if isinstance(fs_size, str):
if fs_size.startswith("0x"):
fs_size_bytes = int(fs_size, 16)
else:
fs_size_bytes = int(fs_size)
else:
fs_size_bytes = int(fs_size)
# LittleFS parameters for ESP32
block_size = 4096
block_count = fs_size_bytes // block_size
# Create LittleFS instance with disk version 2.0 for Tasmota
fs = LittleFS(
block_size=block_size,
block_count=block_count,
disk_version=0x00020000,
mount=True
)
# Add all files from filesystem_dir
source_path = Path(filesystem_dir)
for item in source_path.rglob("*"):
rel_path = item.relative_to(source_path)
if item.is_dir():
fs.makedirs(rel_path.as_posix(), exist_ok=True)
else:
# Ensure parent directories exist
if rel_path.parent != Path("."):
fs.makedirs(rel_path.parent.as_posix(), exist_ok=True)
# Copy file
with fs.open(rel_path.as_posix(), "wb") as dest:
dest.write(item.read_bytes())
# Write filesystem image
with open(output_file, "wb") as f:
f.write(fs.context.buffer)
print()
print(Fore.GREEN + f"LittleFS image created: {output_file}")
return True
def esp32_fetch_safeboot_bin(tasmota_platform):

View File

@ -31,6 +31,7 @@ platform_packages = ${core.platform_packages}
framework = arduino
board = esp8266_1M
board_build.filesystem = littlefs
board_build.littlefs_version = 2.0
board_build.variants_dir = variants/tasmota
custom_unpack_dir = unpacked_littlefs
build_unflags = ${core.build_unflags}
@ -137,13 +138,10 @@ lib_ignore = ESP8266Audio
[core]
; *** Esp8266 Tasmota modified Arduino core based on core 2.7.4. Added Backport for PWM selection
platform = https://github.com/tasmota/platform-espressif8266/releases/download/2025.10.00/platform-espressif8266.zip
platform = https://github.com/tasmota/platform-espressif8266/releases/download/2025.12.00/platform-espressif8266.zip
platform_packages =
build_unflags = ${esp_defaults.build_unflags}
build_flags = ${esp82xx_defaults.build_flags}
; *** Use ONE of the two PWM variants. Tasmota default is Locked PWM
;-DWAVEFORM_LOCKED_PHASE
-DWAVEFORM_LOCKED_PWM

View File

@ -97,7 +97,7 @@ custom_component_remove =
espressif/cmake_utilities
[core32]
platform = https://github.com/tasmota/platform-espressif32/releases/download/2025.12.30/platform-espressif32.zip
platform = https://github.com/tasmota/platform-espressif32/releases/download/2025.12.31/platform-espressif32.zip
platform_packages =
build_unflags = ${esp32_defaults.build_unflags}
build_flags = ${esp32_defaults.build_flags}