5 Commits

21 changed files with 217 additions and 131719 deletions

View File

@@ -1,72 +0,0 @@
name: Deploy firmware via FTP (master)
on:
push:
branches:
- master
concurrency:
group: deploy-master
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

1
.gitignore vendored
View File

@@ -216,3 +216,4 @@ pip-log.txt
#Mr Developer #Mr Developer
.mr.developer.cfg .mr.developer.cfg
src/credentials.h src/credentials.h
platformio_override.ini

100
README.md
View File

@@ -1,58 +1,60 @@
Eggduino # Der Sternenlabor EggBot
==== * Basiert auf EggDuino mit folgenden Anpassungen
* portiert auf ESP32
* Bessere Stepper Library
* Einfache Bahnplanung (weniger Verzerrungen)
* Stift Servo mit Rampensteuerung
* Webinterface für Logging und Einstellungen (Anschlussbelegung)
* BLE Interface (http://Eggbot.app)
* Web Socket Interace (aktuell ungetestet)
* RS232 Kommunikation mit 115200 (zuvor 9600)
* Inkscape Plugin AxiDraw_395 (siehe GIT)
* Modifizierte ebb_serial.py zur Erkennung
* Modifizierte eggbot.py - entfernt Pausen zwischen Bewegungen
* Verwendung ohne Inkscape mittels http://EggBot.app
Arduino Firmware for Eggbot / Spherebot with Inkscape-Integration Ohne angepasstes Inkscape Plugin kann keine Kommunikation zum EggBot aufgenommen werden.
Version 1.6a # Materialliste
tested with Inkscape Portable 0.91, Eggbot Extension and patched eggbot.py
Regards: Eggduino-Firmware by Joachim Cerny, 2015 Schrauben:
* Motor Welle Ei: M3x8
* Motorbefestigung: 8x M3x10
* Motor Welle Stift: M3x12
* Klemme für Eihalterung: 2x M3x16
* Spannschraube Stift: M3x25
* Armgelenk: M3x30
* Mutter: 4x M3 **vierkant!**
* Platine Schrauben: 4x M2.3x5
* Schrauben für Feder: 2x M2.3x5
* Scheiben für Feder: 2x M3x6x0.5
Thanks for the nice libs ACCELSTEPPER and SERIALCOMMAND, which made this project much easier. Thanks to the Eggbot-Team for such a funny and enjoyable concept! Thanks to my wife and my daughter for their patience. :-) Sonstiges:
* Zugfeder: 8x17 (entspannt)
* O-Ring: 3x 18x2
* Kugellager: 608ZZ (8x22x7)
Features: Elektronik:
* 2x Schrittmotor Nema 17 (200 Schritte/Umd.)
* 2x Anschlusskabel
* 1x Micro Servo
* 1x CNC Shield (z.B. AZDelivery)
* 2x Schrittmotor Treiber DRV8825
* 1x ESP32 Arduino UNO Formfaktor
* 1x Netzteil 12V min. 1A
* 1x Micro USB Kabel
- Implemented Eggbot-Protocol-Version 2.1.0 3D-Druckteile: https://www.thingiverse.com/thing:3431363 (hoffentlich bald in unserem GIT)
- Turn-on homing: switch-on position of pen will be taken as reference point.
- No collision-detection!!
- Supported Servos: At least one type ;-) I use Arduino Servo-Lib with TG9e- standard servo.
- Full Arduino-Compatible. I used an Arduino Uno
- Button-support (3 buttons)
Tested and fully functional with Inkscape. # Elektronik
Damit der ESP32 mit angeschlossenem CNC Shield korrekt funktioniert. Muss zwischen Enable und GND ein 1k Widerstand eingelötet werden.
![CNC Mod](res/cnc-shield-mod.png)
Installation: ## Anschlussbelegung
* Ei-Drehen Stepper: Y
- Upload Eggduino.ino with Arduino-IDE or similar tool to your Arudino (i.e. Uno) * Stift-Bewegen Stepper: X
- Disable Autoreset on Arduinoboard (there are several ways to do this... Which one does not matter...) * Servo Ctrl Pin: Z-Step
- Install Inkscape Tools wit Eggbot extension. Detailed instructions: (You yust need to complete Steps 1 and 2) * USB Versorgung an ESP32 UNO (nötig)
http://wiki.evilmadscientist.com/Installing_software * 12V Netzteil an CNC Shield (nicht ESP32 UNO Buche)
- Because of an bug in the Eggbot-extension (Function findEiBotBoards()), the Eggduino cannot be detected by default.
Hopefully, the guys will fix this later on. But we can fix it on our own.
It is quiete easy:
- Go to your Inkscape-Installationfolder and navigate to subfolder .\App\Inkscape\share\extensions
- open File "eggbot.py" in texteditor and search for line:
"Try any devices which seem to have EBB boards attached"
- comment that block with "#" like this:
# Try any devices which seem to have EBB boards attached
# for strComPort in eggbot_scan.findEiBotBoards():
# serialPort = self.testSerialPort( strComPort )
# if serialPort:
# self.svgSerialPort = strComPort
# return serialPort
- In my version lines 1355-1360
## Setup
Add credentials.h file with content like this:
const char *kWifiSsid = "MySSID";
const char *kWifiPassword = "MySecret";
To disable Wifi:
const char *kWifiSsid = 0;
const char *kWifiPassword = 0;
Falls von der Anschlussbelegung abgewichen wird, muss die geänderte Belegung über das Webinterface angepasst werden.

BIN
docs/res/cnc-shield-mod.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -1,28 +0,0 @@
{
"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
}
]
}
]
}

Binary file not shown.

View File

@@ -0,0 +1,14 @@
#pragma once
// Arduino-ESP32 1.0.6 does not define these version macros, but some newer
// libraries use them to select legacy-compatible code paths.
#ifndef ESP_ARDUINO_VERSION
#define EGGDUINO_LEGACY_ARDUINO_ESP32 1
#define ESP_ARDUINO_VERSION 0
#else
#define EGGDUINO_LEGACY_ARDUINO_ESP32 0
#endif
#ifndef ESP_ARDUINO_VERSION_VAL
#define ESP_ARDUINO_VERSION_VAL(major, minor, patch) (((major) * 10000) + ((minor) * 100) + (patch))
#endif

View File

@@ -29,7 +29,7 @@ private:
public: public:
Button(byte p, ActionCb a): debounce(0), state(1), lastState(1), action(a), pin(p) { Button(byte p, ActionCb a): debounce(0), state(1), lastState(1), pin(p), action(a) {
pinMode(pin, INPUT_PULLUP); pinMode(pin, INPUT_PULLUP);
} }
@@ -56,4 +56,3 @@ public:
}; //button }; //button
#endif //__BUTTON_H__ #endif //__BUTTON_H__

View File

@@ -8,34 +8,45 @@
; Please visit documentation for the other options and examples ; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[env:uno] [platformio]
default_envs = uno_linux
extra_configs = platformio_override.ini
[env]
platform = platformio/espressif32 platform = platformio/espressif32
board = esp32dev board = esp32dev
framework = arduino framework = arduino
monitor_speed = 115200 monitor_speed = 115200
lib_deps =
arminjo/ServoEasing
madhephaestus/ESP32Servo@^3.0.6
bblanchon/ArduinoJson@^6.21.5
links2004/WebSockets@^2.6.1
[env:uno_linux]
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 ${env.lib_deps}
madhephaestus/ESP32Servo@^3.0.6 gin66/FastAccelStepper@^0.33.13
bblanchon/ArduinoJson@^6.21.5 h2zero/NimBLE-Arduino@^2.3.6
gin66/FastAccelStepper@^0.33.13
h2zero/NimBLE-Arduino@^2.3.6 [env:uno_windows]
links2004/WebSockets@^2.6.1 extra_scripts = pre:scripts/patch_legacy_esp32_libs.py
build_flags =
-DEGGDUINO_WINDOWS_BUILD_FIXES=1
-include $PROJECT_INCLUDE_DIR/ArduinoEsp32Compat.h
monitor_port = COM*
upload_port = COM8
lib_deps =
${env.lib_deps}
gin66/FastAccelStepper@0.30.15
h2zero/NimBLE-Arduino@2.2.3
[env:uno_macos] [env:uno_macos]
platform = platformio/espressif32
board = esp32dev
framework = arduino
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 ${env.lib_deps}
madhephaestus/ESP32Servo@^3.0.6 gin66/FastAccelStepper@^0.33.13
bblanchon/ArduinoJson@^6.21.5 h2zero/NimBLE-Arduino@^2.3.6
gin66/FastAccelStepper@^0.33.13
h2zero/NimBLE-Arduino@^2.3.6
links2004/WebSockets@^2.6.1

View File

@@ -0,0 +1,5 @@
# Copy file to platformio_override.ini and change default_envs to your needs.
# In this example file it uses the mac configuration.
[platformio]
default_envs = uno_mac

View File

@@ -1,101 +0,0 @@
#!/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)

View File

@@ -0,0 +1,98 @@
from pathlib import Path
Import("env")
def patch_nimble_address() -> None:
libdeps_dir = Path(env.subst("$PROJECT_LIBDEPS_DIR"))
env_name = env.subst("$PIOENV")
source_path = libdeps_dir / env_name / "NimBLE-Arduino" / "src" / "NimBLEAddress.cpp"
if not source_path.exists():
return
original = source_path.read_text(encoding="utf-8")
updated = original
include_old = '# include <algorithm>\n'
include_new = '# include <algorithm>\n# include <cstdlib>\n'
if include_old in updated and "# include <cstdlib>\n" not in updated:
updated = updated.replace(include_old, include_new, 1)
call_old = " uint64_t address = std::stoull(mac, nullptr, 16);"
call_new = " uint64_t address = strtoull(mac.c_str(), nullptr, 16);"
updated = updated.replace(call_old, call_new, 1)
if updated != original:
source_path.write_text(updated, encoding="utf-8")
print("Patched NimBLE-Arduino for legacy ESP32 toolchain compatibility")
def patch_nimble_device() -> None:
libdeps_dir = Path(env.subst("$PROJECT_LIBDEPS_DIR"))
env_name = env.subst("$PIOENV")
source_path = libdeps_dir / env_name / "NimBLE-Arduino" / "src" / "NimBLEDevice.cpp"
if not source_path.exists():
return
original = source_path.read_text(encoding="utf-8")
updated = original
updated = updated.replace(
" ble_sm_io pkey{.action = BLE_SM_IOACT_INPUT, .passkey = passkey};\n",
" ble_sm_io pkey{};\n"
" pkey.action = BLE_SM_IOACT_INPUT;\n"
" pkey.passkey = passkey;\n",
1,
)
updated = updated.replace(
" ble_sm_io pkey{.action = BLE_SM_IOACT_NUMCMP, .numcmp_accept = accept};\n",
" ble_sm_io pkey{};\n"
" pkey.action = BLE_SM_IOACT_NUMCMP;\n"
" pkey.numcmp_accept = accept;\n",
1,
)
if updated != original:
source_path.write_text(updated, encoding="utf-8")
print("Patched NimBLEDevice.cpp for legacy ESP32 toolchain compatibility")
def patch_websockets_client() -> None:
libdeps_dir = Path(env.subst("$PROJECT_LIBDEPS_DIR"))
env_name = env.subst("$PIOENV")
source_path = libdeps_dir / env_name / "WebSockets" / "src" / "WebSocketsClient.cpp"
if not source_path.exists():
return
original = source_path.read_text(encoding="utf-8")
updated = original
old = (
"#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 4)\n"
" _client.ssl->setCACertBundle(_CA_bundle, _CA_bundle_size);\n"
"#else\n"
" _client.ssl->setCACertBundle(_CA_bundle);\n"
"#endif\n"
)
new = (
"#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 4)\n"
" _client.ssl->setCACertBundle(_CA_bundle, _CA_bundle_size);\n"
"#else\n"
" // Arduino-ESP32 1.x has no CA bundle API; the project only uses\n"
" // WebSocketsServer, so keep the client path buildable with insecure TLS.\n"
" _client.ssl->setInsecure();\n"
"#endif\n"
)
updated = updated.replace(old, new, 1)
if updated != original:
source_path.write_text(updated, encoding="utf-8")
print("Patched WebSocketsClient.cpp for legacy ESP32 toolchain compatibility")
patch_nimble_address()
patch_nimble_device()
patch_websockets_client()

View File

@@ -144,9 +144,7 @@ void setPen()
{ {
Log(__FUNCTION__); Log(__FUNCTION__);
int cmd; int cmd;
int value;
char *arg; char *arg;
char cstrMsg[20];
// moveToDestination(); // moveToDestination();
@@ -172,7 +170,7 @@ void setPen()
val = nextCommandArg(); val = nextCommandArg();
if (val != NULL) if (val != NULL)
{ {
value = atoi(val); (void)atoi(val);
sendAck(); sendAck();
// delay(value); // delay(value);
} }
@@ -189,8 +187,7 @@ void setPen()
void togglePen() void togglePen()
{ {
Log(__FUNCTION__); Log(__FUNCTION__);
char *arg; (void)nextCommandArg();
arg = nextCommandArg();
doTogglePen(); doTogglePen();
sendAck(); sendAck();

13
src/esp_timer_compat.c Normal file
View File

@@ -0,0 +1,13 @@
#if defined(EGGDUINO_WINDOWS_BUILD_FIXES)
#include <ArduinoEsp32Compat.h>
#if defined(ESP32) && EGGDUINO_LEGACY_ARDUINO_ESP32
#include <esp_timer.h>
int esp_timer_is_active(esp_timer_handle_t timer)
{
(void)timer;
return 0;
}
#endif
#endif

View File

@@ -1,23 +0,0 @@
import re
import unittest
from pathlib import Path
class DeployWorkflowTests(unittest.TestCase):
def test_deploy_workflow_triggers_on_master_pushes(self):
workflow_path = Path(__file__).resolve().parents[1] / ".gitea" / "workflows" / "deploy-ftp.yml"
workflow_text = workflow_path.read_text(encoding="utf-8")
branch_block_match = re.search(
r"on:\s*\n\s*push:\s*\n\s*branches:\s*\n(?P<branches>(?:\s*-\s*[^\n]+\n)+)",
workflow_text,
)
self.assertIsNotNone(branch_block_match, "workflow should define push branches")
branches = re.findall(r"-\s*([^\n]+)", branch_block_match.group("branches"))
self.assertEqual(branches, ["master"])
if __name__ == "__main__":
unittest.main()

View File

@@ -1,83 +0,0 @@
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()