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 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**_
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user