Auto stash before merge of "SlEggBotEL32Cfg" and "origin/SlEggBotEL32Cfg"
This commit is contained in:
72
.gitea/workflows/deploy-ftp.yml
Normal file
72
.gitea/workflows/deploy-ftp.yml
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
name: Deploy firmware via FTP (main)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-main
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Build and FTP Sync
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Install PlatformIO
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install platformio
|
||||||
|
|
||||||
|
- name: Build firmware bundle
|
||||||
|
run: pio run -e uno
|
||||||
|
|
||||||
|
- name: Install lftp client
|
||||||
|
run: |
|
||||||
|
if command -v lftp >/dev/null 2>&1; then
|
||||||
|
echo 'lftp is already available on this runner image'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v sudo >/dev/null 2>&1; then
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y lftp
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y lftp
|
||||||
|
|
||||||
|
- name: Upload firmware bundle via lftp
|
||||||
|
env:
|
||||||
|
FTP_SERVER: ${{ secrets.FTP_SERVER }}
|
||||||
|
FTP_USERNAME: ${{ secrets.FTP_USERNAME }}
|
||||||
|
FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }}
|
||||||
|
FTP_SERVER_DIR: ${{ secrets.FTP_SERVER_DIR }}
|
||||||
|
run: |
|
||||||
|
lftp <<EOF
|
||||||
|
set cmd:fail-exit true
|
||||||
|
set net:timeout 120
|
||||||
|
set net:max-retries 2
|
||||||
|
set net:reconnect-interval-base 5
|
||||||
|
set ftp:passive-mode true
|
||||||
|
set ftp:prefer-epsv false
|
||||||
|
set ftp:ssl-force true
|
||||||
|
set ftp:ssl-protect-data true
|
||||||
|
set ftp:ssl-auth TLS
|
||||||
|
set ssl:verify-certificate no
|
||||||
|
open -u "$FTP_USERNAME","$FTP_PASSWORD" -p 21 "$FTP_SERVER"
|
||||||
|
mirror -R --verbose --delete \
|
||||||
|
--exclude-glob .DS_Store \
|
||||||
|
firmware/ "$FTP_SERVER_DIR"
|
||||||
|
quit
|
||||||
|
EOF
|
||||||
BIN
firmware/boot_app0.bin
Normal file
BIN
firmware/boot_app0.bin
Normal file
Binary file not shown.
BIN
firmware/bootloader.bin
Normal file
BIN
firmware/bootloader.bin
Normal file
Binary file not shown.
BIN
firmware/firmware.bin
Normal file
BIN
firmware/firmware.bin
Normal file
Binary file not shown.
BIN
firmware/firmware.elf
Executable file
BIN
firmware/firmware.elf
Executable file
Binary file not shown.
131335
firmware/firmware.map
Normal file
131335
firmware/firmware.map
Normal file
File diff suppressed because one or more lines are too long
28
firmware/manifest.json
Normal file
28
firmware/manifest.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "EggDuino ESP32 Firmware",
|
||||||
|
"version": "1.6a",
|
||||||
|
"new_install_prompt_erase": true,
|
||||||
|
"builds": [
|
||||||
|
{
|
||||||
|
"chipFamily": "ESP32",
|
||||||
|
"parts": [
|
||||||
|
{
|
||||||
|
"path": "bootloader.bin",
|
||||||
|
"offset": 4096
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "partitions.bin",
|
||||||
|
"offset": 32768
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "boot_app0.bin",
|
||||||
|
"offset": 57344
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "firmware.bin",
|
||||||
|
"offset": 65536
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
firmware/partitions.bin
Normal file
BIN
firmware/partitions.bin
Normal file
Binary file not shown.
@@ -15,6 +15,7 @@ framework = arduino
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 576000
|
upload_speed = 576000
|
||||||
upload_port = /dev/ttyUSB*
|
upload_port = /dev/ttyUSB*
|
||||||
|
extra_scripts = post:scripts/package_firmware.py
|
||||||
lib_deps =
|
lib_deps =
|
||||||
arminjo/ServoEasing
|
arminjo/ServoEasing
|
||||||
madhephaestus/ESP32Servo@^3.0.6
|
madhephaestus/ESP32Servo@^3.0.6
|
||||||
@@ -30,6 +31,7 @@ framework = arduino
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_port = /dev/cu.usb*
|
monitor_port = /dev/cu.usb*
|
||||||
upload_port = /dev/cu.usb*
|
upload_port = /dev/cu.usb*
|
||||||
|
extra_scripts = post:scripts/package_firmware.py
|
||||||
lib_deps =
|
lib_deps =
|
||||||
arminjo/ServoEasing
|
arminjo/ServoEasing
|
||||||
madhephaestus/ESP32Servo@^3.0.6
|
madhephaestus/ESP32Servo@^3.0.6
|
||||||
|
|||||||
101
scripts/package_firmware.py
Normal file
101
scripts/package_firmware.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
BUILD_ARTIFACTS = (
|
||||||
|
"bootloader.bin",
|
||||||
|
"partitions.bin",
|
||||||
|
"firmware.bin",
|
||||||
|
"firmware.elf",
|
||||||
|
"firmware.map",
|
||||||
|
)
|
||||||
|
|
||||||
|
MANIFEST_PARTS = (
|
||||||
|
{"path": "bootloader.bin", "offset": 4096},
|
||||||
|
{"path": "partitions.bin", "offset": 32768},
|
||||||
|
{"path": "boot_app0.bin", "offset": 57344},
|
||||||
|
{"path": "firmware.bin", "offset": 65536},
|
||||||
|
)
|
||||||
|
|
||||||
|
VERSION_PATTERN = re.compile(r'Eggduino-Firmware V([^"\\]+)')
|
||||||
|
|
||||||
|
|
||||||
|
def extract_firmware_version(header_text: str) -> str:
|
||||||
|
match = VERSION_PATTERN.search(header_text)
|
||||||
|
if not match:
|
||||||
|
raise ValueError("Could not extract firmware version from include/EggDuino.h")
|
||||||
|
return match.group(1).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def build_manifest(version: str) -> dict:
|
||||||
|
return {
|
||||||
|
"name": "EggDuino ESP32 Firmware",
|
||||||
|
"version": version,
|
||||||
|
"new_install_prompt_erase": True,
|
||||||
|
"builds": [
|
||||||
|
{
|
||||||
|
"chipFamily": "ESP32",
|
||||||
|
"parts": list(MANIFEST_PARTS),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def package_bundle(build_dir: Path, boot_app0_path: Path, header_path: Path, output_dir: Path) -> None:
|
||||||
|
build_dir = Path(build_dir)
|
||||||
|
boot_app0_path = Path(boot_app0_path)
|
||||||
|
header_path = Path(header_path)
|
||||||
|
output_dir = Path(output_dir)
|
||||||
|
|
||||||
|
if output_dir.exists():
|
||||||
|
shutil.rmtree(output_dir)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
for artifact_name in BUILD_ARTIFACTS:
|
||||||
|
artifact_path = build_dir / artifact_name
|
||||||
|
if not artifact_path.is_file():
|
||||||
|
raise FileNotFoundError(f"Missing build artifact: {artifact_path}")
|
||||||
|
shutil.copy2(artifact_path, output_dir / artifact_name)
|
||||||
|
|
||||||
|
if not boot_app0_path.is_file():
|
||||||
|
raise FileNotFoundError(f"Missing boot_app0.bin: {boot_app0_path}")
|
||||||
|
shutil.copy2(boot_app0_path, output_dir / "boot_app0.bin")
|
||||||
|
|
||||||
|
version = extract_firmware_version(header_path.read_text(encoding="utf-8"))
|
||||||
|
manifest_path = output_dir / "manifest.json"
|
||||||
|
manifest_path.write_text(
|
||||||
|
json.dumps(build_manifest(version), indent=4) + "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_boot_app0_path(env) -> Path:
|
||||||
|
framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif32")
|
||||||
|
if not framework_dir:
|
||||||
|
raise FileNotFoundError("PlatformIO framework-arduinoespressif32 package is not installed")
|
||||||
|
|
||||||
|
boot_app0_path = Path(framework_dir) / "tools" / "partitions" / "boot_app0.bin"
|
||||||
|
if not boot_app0_path.is_file():
|
||||||
|
raise FileNotFoundError(f"Could not find boot_app0.bin in {boot_app0_path.parent}")
|
||||||
|
return boot_app0_path
|
||||||
|
|
||||||
|
|
||||||
|
def _package_platformio_bundle(target, source, env) -> None:
|
||||||
|
del target, source
|
||||||
|
project_dir = Path(env.subst("$PROJECT_DIR"))
|
||||||
|
package_bundle(
|
||||||
|
build_dir=Path(env.subst("$BUILD_DIR")),
|
||||||
|
boot_app0_path=_get_boot_app0_path(env),
|
||||||
|
header_path=project_dir / "include" / "EggDuino.h",
|
||||||
|
output_dir=project_dir / "firmware",
|
||||||
|
)
|
||||||
|
print(f"Packaged firmware bundle to {project_dir / 'firmware'}")
|
||||||
|
|
||||||
|
|
||||||
|
if "Import" in globals():
|
||||||
|
Import("env")
|
||||||
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", _package_platformio_bundle)
|
||||||
83
tests/test_package_firmware.py
Normal file
83
tests/test_package_firmware.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from scripts.package_firmware import build_manifest, extract_firmware_version, package_bundle
|
||||||
|
|
||||||
|
|
||||||
|
class PackageFirmwareTests(unittest.TestCase):
|
||||||
|
def test_extract_firmware_version_reads_init_string(self):
|
||||||
|
header_text = '#define initSting "EBBv13_and_above Protocol emulated by Eggduino-Firmware V1.6a"\n'
|
||||||
|
|
||||||
|
self.assertEqual(extract_firmware_version(header_text), "1.6a")
|
||||||
|
|
||||||
|
def test_build_manifest_matches_expected_esp32_offsets(self):
|
||||||
|
manifest = build_manifest("1.6a")
|
||||||
|
|
||||||
|
self.assertEqual(manifest["name"], "EggDuino ESP32 Firmware")
|
||||||
|
self.assertEqual(manifest["version"], "1.6a")
|
||||||
|
self.assertTrue(manifest["new_install_prompt_erase"])
|
||||||
|
self.assertEqual(manifest["builds"][0]["chipFamily"], "ESP32")
|
||||||
|
self.assertEqual(
|
||||||
|
manifest["builds"][0]["parts"],
|
||||||
|
[
|
||||||
|
{"path": "bootloader.bin", "offset": 4096},
|
||||||
|
{"path": "partitions.bin", "offset": 32768},
|
||||||
|
{"path": "boot_app0.bin", "offset": 57344},
|
||||||
|
{"path": "firmware.bin", "offset": 65536},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_package_bundle_copies_outputs_and_removes_stale_files(self):
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
temp_path = Path(temp_dir)
|
||||||
|
build_dir = temp_path / "build"
|
||||||
|
build_dir.mkdir()
|
||||||
|
header_path = temp_path / "EggDuino.h"
|
||||||
|
output_dir = temp_path / "firmware"
|
||||||
|
output_dir.mkdir()
|
||||||
|
boot_app0_path = temp_path / "boot_app0.bin"
|
||||||
|
|
||||||
|
build_artifacts = {
|
||||||
|
"bootloader.bin": "bootloader",
|
||||||
|
"partitions.bin": "partitions",
|
||||||
|
"firmware.bin": "firmware-bin",
|
||||||
|
"firmware.elf": "firmware-elf",
|
||||||
|
"firmware.map": "firmware-map",
|
||||||
|
}
|
||||||
|
for file_name, contents in build_artifacts.items():
|
||||||
|
(build_dir / file_name).write_text(contents, encoding="utf-8")
|
||||||
|
|
||||||
|
header_path.write_text(
|
||||||
|
'#define initSting "EBBv13_and_above Protocol emulated by Eggduino-Firmware V1.6b"\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
boot_app0_path.write_text("boot-app0", encoding="utf-8")
|
||||||
|
(output_dir / "stale.txt").write_text("old", encoding="utf-8")
|
||||||
|
|
||||||
|
package_bundle(
|
||||||
|
build_dir=build_dir,
|
||||||
|
boot_app0_path=boot_app0_path,
|
||||||
|
header_path=header_path,
|
||||||
|
output_dir=output_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertFalse((output_dir / "stale.txt").exists())
|
||||||
|
expected_files = {
|
||||||
|
"bootloader.bin": "bootloader",
|
||||||
|
"partitions.bin": "partitions",
|
||||||
|
"firmware.bin": "firmware-bin",
|
||||||
|
"firmware.elf": "firmware-elf",
|
||||||
|
"firmware.map": "firmware-map",
|
||||||
|
"boot_app0.bin": "boot-app0",
|
||||||
|
}
|
||||||
|
for file_name, contents in expected_files.items():
|
||||||
|
self.assertEqual((output_dir / file_name).read_text(encoding="utf-8"), contents)
|
||||||
|
|
||||||
|
manifest = json.loads((output_dir / "manifest.json").read_text(encoding="utf-8"))
|
||||||
|
self.assertEqual(manifest["version"], "1.6b")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user