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 relevant files were touched
- [ ] Only one feature/fix was added per PR and the code change compiles without warnings - [ ] 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 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). - [ ] 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**_ _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> # Written by Maximilian Gerhardt <maximilian.gerhardt@rub.de>
# 29th December 2020 # 29th December 2020
# and Christian Baars, Johann Obermeier # and Christian Baars, Johann Obermeier
# 2023 / 2024 # 2023 - 2025
# License: Apache # License: Apache
# Expanded from functionality provided by PlatformIO's espressif32 and espressif8266 platforms, credited below. # 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 # 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" # run by either using the VSCode task "Custom" -> "Download Filesystem"
# or by doing 'pio run -t downloadfs' (with optional '-e <environment>') from the commandline. # 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. # 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 # this folder can be changed by writing 'custom_unpack_dir = some_other_dir' in the corresponding platformio.ini
# environment. # environment.
import re import re
import sys
from os.path import isfile, join from os.path import isfile, join
from enum import Enum from enum import Enum
import os import os
@ -20,7 +19,9 @@ import tasmotapiolib
import subprocess import subprocess
import shutil import shutil
import json import json
from pathlib import Path
from colorama import Fore, Back, Style from colorama import Fore, Back, Style
from littlefs import LittleFS
from platformio.compat import IS_WINDOWS from platformio.compat import IS_WINDOWS
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
@ -42,19 +43,12 @@ class FSInfo:
self.block_size = block_size self.block_size = block_size
def __repr__(self): 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}" 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): class FS_Info(FSInfo):
def __init__(self, start, length, page_size, block_size): 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) super().__init__(FSType.LITTLEFS, start, length, page_size, block_size)
def __repr__(self): 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)}" 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): def _parse_size(value):
if isinstance(value, int): if isinstance(value, int):
@ -150,15 +144,11 @@ switch_off_ldf()
## Script interface functions ## Script interface functions
def parse_partition_table(content): def parse_partition_table(content):
entries = [e for e in content.split(b'\xaaP') if len(e) > 0] entries = [e for e in content.split(b'\xaaP') if len(e) > 0]
#print("Partition data:")
for entry in entries: for entry in entries:
type = entry[1] type = entry[1]
if type in [0x82,0x83]: # SPIFFS or LITTLEFS if type in [0x82,0x83]: # SPIFFS or LITTLEFS
offset = int.from_bytes(entry[2:5], byteorder='little', signed=False) offset = int.from_bytes(entry[2:6], byteorder='little', signed=False)
size = int.from_bytes(entry[6:9], byteorder='little', signed=False) size = int.from_bytes(entry[6:10], byteorder='little', signed=False)
#print("type:",hex(type))
#print("address:",hex(offset))
#print("size:",hex(size))
env["FS_START"] = offset env["FS_START"] = offset
env["FS_SIZE"] = size env["FS_SIZE"] = size
env["FS_PAGE"] = int("0x100", 16) 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): if not os.path.exists(unpack_dir):
os.makedirs(unpack_dir) os.makedirs(unpack_dir)
cmd = fs_info.get_extract_cmd(downloaded_file, unpack_dir) print()
print("Unpack files from filesystem image")
try: 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) return (True, unpack_dir)
except subprocess.CalledProcessError as exc: except Exception as exc:
print("Unpacking filesystem failed with " + str(exc)) print("Unpacking filesystem with littlefs-python failed with " + str(exc))
return (False, "") return (False, "")
def display_fs(extracted_dir): def display_fs(extracted_dir):
# extract command already nicely lists all extracted files. # List all extracted files
# no need to display that ourselves. just display a summary file_count = 0
file_count = sum([len(files) for r, d, files in os.walk(extracted_dir)]) print(Fore.GREEN + "Extracted files from filesystem image:")
print("Extracted " + str(file_count) + " file(s) from filesystem.") 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): def command_download_fs(*args, **kwargs):
info = get_fs_type_start_and_length() info = get_fs_type_start_and_length()

View File

@ -24,10 +24,12 @@ from genericpath import exists
import os import os
from os.path import join, getsize from os.path import join, getsize
import csv import csv
from littlefs import LittleFS
import requests import requests
import shutil import shutil
import subprocess import subprocess
import codecs import codecs
from pathlib import Path
from colorama import Fore from colorama import Fore
from SCons.Script import COMMAND_LINE_TARGETS from SCons.Script import COMMAND_LINE_TARGETS
@ -134,7 +136,7 @@ def esp32_build_filesystem(fs_size):
os.makedirs(filesystem_dir) os.makedirs(filesystem_dir)
if num_entries > 1: if num_entries > 1:
print() print()
print(Fore.GREEN + "Will create filesystem with the following files:") print(Fore.GREEN + "Will create filesystem with the following file(s):")
print() print()
for file in files: for file in files:
if "no_files" in file: if "no_files" in file:
@ -146,21 +148,66 @@ def esp32_build_filesystem(fs_size):
if len(file.split(" ")) > 1: if len(file.split(" ")) > 1:
target = os.path.normpath(join(filesystem_dir, 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]) 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) open(target, "wb").write(response.content)
else: else:
print(Fore.RED + "Failed to download: ",file) print(Fore.RED + "Failed to download: ",file)
continue continue
if os.path.isdir(file): if os.path.isdir(file):
print(f"{file}/ (directory)")
shutil.copytree(file, filesystem_dir, dirs_exist_ok=True) shutil.copytree(file, filesystem_dir, dirs_exist_ok=True)
else: else:
print(file)
shutil.copy(file, filesystem_dir) shutil.copy(file, filesystem_dir)
if not os.listdir(filesystem_dir): if not os.listdir(filesystem_dir):
#print("No files added -> will NOT create littlefs.bin and NOT overwrite fs partition!") #print("No files added -> will NOT create littlefs.bin and NOT overwrite fs partition!")
return False return False
tool = env.subst(env["MKFSTOOL"])
cmd = (tool,"-c",filesystem_dir,"-s",fs_size,join(env.subst("$BUILD_DIR"),"littlefs.bin")) # Use littlefs-python
returncode = subprocess.call(cmd, shell=False) output_file = join(env.subst("$BUILD_DIR"), "littlefs.bin")
# print(returncode)
# 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 return True
def esp32_fetch_safeboot_bin(tasmota_platform): def esp32_fetch_safeboot_bin(tasmota_platform):

View File

@ -31,6 +31,7 @@ platform_packages = ${core.platform_packages}
framework = arduino framework = arduino
board = esp8266_1M board = esp8266_1M
board_build.filesystem = littlefs board_build.filesystem = littlefs
board_build.littlefs_version = 2.0
board_build.variants_dir = variants/tasmota board_build.variants_dir = variants/tasmota
custom_unpack_dir = unpacked_littlefs custom_unpack_dir = unpacked_littlefs
build_unflags = ${core.build_unflags} build_unflags = ${core.build_unflags}
@ -137,13 +138,10 @@ lib_ignore = ESP8266Audio
[core] [core]
; *** Esp8266 Tasmota modified Arduino core based on core 2.7.4. Added Backport for PWM selection ; *** 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 = platform_packages =
build_unflags = ${esp_defaults.build_unflags} build_unflags = ${esp_defaults.build_unflags}
build_flags = ${esp82xx_defaults.build_flags} build_flags = ${esp82xx_defaults.build_flags}
; *** Use ONE of the two PWM variants. Tasmota default is Locked PWM ; *** Use ONE of the two PWM variants. Tasmota default is Locked PWM
;-DWAVEFORM_LOCKED_PHASE ;-DWAVEFORM_LOCKED_PHASE
-DWAVEFORM_LOCKED_PWM -DWAVEFORM_LOCKED_PWM

View File

@ -97,7 +97,7 @@ custom_component_remove =
espressif/cmake_utilities espressif/cmake_utilities
[core32] [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 = platform_packages =
build_unflags = ${esp32_defaults.build_unflags} build_unflags = ${esp32_defaults.build_unflags}
build_flags = ${esp32_defaults.build_flags} build_flags = ${esp32_defaults.build_flags}