Platform 2025.12.31
This commit is contained in:
parent
30cef9c501
commit
54a152f440
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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**_
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user