Barmaja-PyArduino 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Barmaja_PyArduino/BARMAJA_PyArduino.ico +0 -0
- Barmaja_PyArduino/__init__.py +528 -0
- Barmaja_PyArduino/__init__.pyi +331 -0
- Barmaja_PyArduino/__main__.py +2 -0
- Barmaja_PyArduino/adapters/__init__.py +35 -0
- Barmaja_PyArduino/api_registry.py +459 -0
- Barmaja_PyArduino/backend/__init__.py +3 -0
- Barmaja_PyArduino/backend/uno_cpp.py +1680 -0
- Barmaja_PyArduino/board.py +80 -0
- Barmaja_PyArduino/cli.py +223 -0
- Barmaja_PyArduino/compiler.py +1721 -0
- Barmaja_PyArduino/console.py +217 -0
- Barmaja_PyArduino/device_packs.py +367 -0
- Barmaja_PyArduino/device_packs_builtin/Button/arduino/Barmaja_Button/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/Button/arduino/Barmaja_Button/src/Barmaja_Button.h +18 -0
- Barmaja_PyArduino/device_packs_builtin/Button/example.py +10 -0
- Barmaja_PyArduino/device_packs_builtin/Button/pack.json +40 -0
- Barmaja_PyArduino/device_packs_builtin/ClockDS3231/arduino/Barmaja_ClockDS3231/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/ClockDS3231/arduino/Barmaja_ClockDS3231/src/Barmaja_ClockDS3231.h +20 -0
- Barmaja_PyArduino/device_packs_builtin/ClockDS3231/example.py +8 -0
- Barmaja_PyArduino/device_packs_builtin/ClockDS3231/pack.json +136 -0
- Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/arduino/Barmaja_DistanceVL53L0X/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/arduino/Barmaja_DistanceVL53L0X/src/Barmaja_DistanceVL53L0X.h +12 -0
- Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/example.py +8 -0
- Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/pack.json +48 -0
- Barmaja_PyArduino/device_packs_builtin/LCDParallel/arduino/Barmaja_LCDParallel/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/LCDParallel/arduino/Barmaja_LCDParallel/src/Barmaja_LCDParallel.h +17 -0
- Barmaja_PyArduino/device_packs_builtin/LCDParallel/example.py +10 -0
- Barmaja_PyArduino/device_packs_builtin/LCDParallel/pack.json +137 -0
- Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/arduino/Barmaja_LoadCellHX711/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/arduino/Barmaja_LoadCellHX711/src/Barmaja_LoadCellHX711.h +16 -0
- Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/example.py +11 -0
- Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/pack.json +74 -0
- Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/arduino/Barmaja_NeoPixelStrip/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/arduino/Barmaja_NeoPixelStrip/src/Barmaja_NeoPixelStrip.h +15 -0
- Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/example.py +13 -0
- Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/pack.json +114 -0
- Barmaja_PyArduino/device_packs_builtin/OLEDText/arduino/Barmaja_OLEDText/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/OLEDText/arduino/Barmaja_OLEDText/src/Barmaja_OLEDText.h +21 -0
- Barmaja_PyArduino/device_packs_builtin/OLEDText/example.py +9 -0
- Barmaja_PyArduino/device_packs_builtin/OLEDText/pack.json +103 -0
- Barmaja_PyArduino/device_packs_builtin/PressureBMP280/arduino/Barmaja_PressureBMP280/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/PressureBMP280/arduino/Barmaja_PressureBMP280/src/Barmaja_PressureBMP280.h +16 -0
- Barmaja_PyArduino/device_packs_builtin/PressureBMP280/example.py +8 -0
- Barmaja_PyArduino/device_packs_builtin/PressureBMP280/pack.json +92 -0
- Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/arduino/Barmaja_RotaryEncoder/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/arduino/Barmaja_RotaryEncoder/src/Barmaja_RotaryEncoder.h +18 -0
- Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/example.py +10 -0
- Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/pack.json +44 -0
- Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/arduino/Barmaja_ServoDriverPCA9685/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/arduino/Barmaja_ServoDriverPCA9685/src/Barmaja_ServoDriverPCA9685.h +15 -0
- Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/example.py +10 -0
- Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/pack.json +117 -0
- Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/arduino/Barmaja_ShiftRegister595/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/arduino/Barmaja_ShiftRegister595/src/Barmaja_ShiftRegister595.h +14 -0
- Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/example.py +9 -0
- Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/pack.json +83 -0
- Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/arduino/Barmaja_TemperatureDS18B20/library.properties +9 -0
- Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/arduino/Barmaja_TemperatureDS18B20/src/Barmaja_TemperatureDS18B20.h +16 -0
- Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/example.py +8 -0
- Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/pack.json +63 -0
- Barmaja_PyArduino/doctor.py +406 -0
- Barmaja_PyArduino/environment.py +595 -0
- Barmaja_PyArduino/errors.py +166 -0
- Barmaja_PyArduino/flow.py +754 -0
- Barmaja_PyArduino/frontend.py +611 -0
- Barmaja_PyArduino/installer.py +151 -0
- Barmaja_PyArduino/ir.py +660 -0
- Barmaja_PyArduino/library_adder.py +440 -0
- Barmaja_PyArduino/live.py +859 -0
- Barmaja_PyArduino/lowering.py +596 -0
- Barmaja_PyArduino/numeric.py +748 -0
- Barmaja_PyArduino/pack_builder.py +544 -0
- Barmaja_PyArduino/py.typed +0 -0
- Barmaja_PyArduino/serial_monitor.py +172 -0
- Barmaja_PyArduino/source.py +184 -0
- Barmaja_PyArduino/stubgen.py +148 -0
- Barmaja_PyArduino/symbols.py +646 -0
- Barmaja_PyArduino/target.py +1631 -0
- Barmaja_PyArduino/toolchain.py +506 -0
- Barmaja_PyArduino/typesys.py +917 -0
- Barmaja_PyArduino/workflow.py +213 -0
- barmaja_pyarduino-2.0.0.dist-info/METADATA +39 -0
- barmaja_pyarduino-2.0.0.dist-info/RECORD +87 -0
- barmaja_pyarduino-2.0.0.dist-info/WHEEL +5 -0
- barmaja_pyarduino-2.0.0.dist-info/licenses/LICENSE.txt +7 -0
- barmaja_pyarduino-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
"""BARMAJA Library Adder: add any Arduino library to BARMAJA PyArduino (upload() mode).
|
|
2
|
+
|
|
3
|
+
Window: python -m Barmaja_PyArduino add-library
|
|
4
|
+
Headless: python -m Barmaja_PyArduino add-library <library.zip|folder> --auto [--class X] [--name Y] [--no-compile]
|
|
5
|
+
Remove: python -m Barmaja_PyArduino remove-pack <StudentClass>
|
|
6
|
+
|
|
7
|
+
What "Add" does on this PC (no internet, no admin rights needed for the BARMAJA folder):
|
|
8
|
+
* copies the Arduino library to <BARMAJA root>/arduino-libraries/<folder>
|
|
9
|
+
* writes the device pack to <BARMAJA root>/device-packs/<StudentClass>
|
|
10
|
+
* replaced items are moved to <BARMAJA root>/backups/ (never deleted)
|
|
11
|
+
The next program run (PyCharm, IDLE, PictoBlox) can use the new class immediately.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
import textwrap
|
|
22
|
+
import threading
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from . import pack_builder as pb
|
|
27
|
+
|
|
28
|
+
ADDED_MARKER = ".barmaja_added_by_library_adder"
|
|
29
|
+
PKG_PARENT = str(Path(__file__).resolve().parent.parent)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------- paths / install / remove ----------------
|
|
33
|
+
def runtime_paths() -> dict[str, Path]:
|
|
34
|
+
from .environment import ensure_runtime_dirs
|
|
35
|
+
root = ensure_runtime_dirs()["root"]
|
|
36
|
+
return {"root": root, "libraries": root / "arduino-libraries", "packs": root / "device-packs", "backups": root / "backups"}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _backup(path: Path, paths: dict[str, Path]) -> Path:
|
|
40
|
+
stamp = time.strftime('%Y%m%d-%H%M%S')
|
|
41
|
+
dest = paths["backups"] / f"{path.name}-{stamp}"
|
|
42
|
+
n = 1
|
|
43
|
+
while dest.exists():
|
|
44
|
+
n += 1
|
|
45
|
+
dest = paths["backups"] / f"{path.name}-{stamp}-{n}"
|
|
46
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
shutil.move(str(path), str(dest))
|
|
48
|
+
return dest
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _lib_version(folder: Path) -> str:
|
|
52
|
+
props = folder / "library.properties"
|
|
53
|
+
if props.is_file():
|
|
54
|
+
for line in props.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
55
|
+
if line.startswith("version="):
|
|
56
|
+
return line.split("=", 1)[1].strip()
|
|
57
|
+
return "?"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def installed_library_names() -> dict[str, Path]:
|
|
61
|
+
"""library.properties name (lower-case) -> folder, from every BARMAJA library root."""
|
|
62
|
+
from .toolchain import pinned_library_dirs
|
|
63
|
+
found: dict[str, Path] = {}
|
|
64
|
+
for folder in pinned_library_dirs():
|
|
65
|
+
props = folder / "library.properties"
|
|
66
|
+
try:
|
|
67
|
+
for line in props.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
68
|
+
if line.startswith("name="):
|
|
69
|
+
found[line.split("=", 1)[1].strip().lower()] = folder
|
|
70
|
+
except OSError:
|
|
71
|
+
pass
|
|
72
|
+
found.setdefault(folder.name.lower(), folder)
|
|
73
|
+
return found
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def missing_dependencies(lib: pb.LibraryScan) -> list[str]:
|
|
77
|
+
have = installed_library_names()
|
|
78
|
+
return [d for d in lib.depends if d.lower() not in have and d.replace(" ", "_").lower() not in have]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def install_library_only(lib: pb.LibraryScan, *, replace_library: bool = False) -> list[str]:
|
|
82
|
+
"""Copy just the Arduino library (e.g. a dependency such as 'Adafruit BusIO')."""
|
|
83
|
+
return install(lib, None, replace_library=replace_library)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def install(lib: pb.LibraryScan, pack_folder: Path | None, *, replace_library: bool = False) -> list[str]:
|
|
87
|
+
paths = runtime_paths()
|
|
88
|
+
out: list[str] = []
|
|
89
|
+
paths["libraries"].mkdir(parents=True, exist_ok=True)
|
|
90
|
+
paths["packs"].mkdir(parents=True, exist_ok=True)
|
|
91
|
+
target = paths["libraries"] / lib.folder_name
|
|
92
|
+
if target.exists():
|
|
93
|
+
have = _lib_version(target)
|
|
94
|
+
if have == lib.version and not replace_library:
|
|
95
|
+
out.append(f"Library {lib.folder_name} {have} is already installed - kept.")
|
|
96
|
+
elif not (target / ADDED_MARKER).exists() and not replace_library:
|
|
97
|
+
raise RuntimeError(f"A different {lib.folder_name} ({have}) is already part of BARMAJA. "
|
|
98
|
+
"It was not replaced, to protect existing projects.")
|
|
99
|
+
else:
|
|
100
|
+
out.append(f"Old library moved to {_backup(target, paths)}")
|
|
101
|
+
target = paths["libraries"] / lib.folder_name
|
|
102
|
+
if not target.exists():
|
|
103
|
+
shutil.copytree(lib.root, target, ignore=shutil.ignore_patterns(".*", "examples", "extras", "test", "tests", "docs"))
|
|
104
|
+
(target / ADDED_MARKER).write_text(time.strftime("%Y-%m-%d %H:%M:%S"), encoding="utf-8")
|
|
105
|
+
out.append(f"Library copied to {target}")
|
|
106
|
+
if pack_folder is None:
|
|
107
|
+
out.append("DONE. Library files installed (no student class).")
|
|
108
|
+
return out
|
|
109
|
+
spec = json.loads((pack_folder / "pack.json").read_text(encoding="utf-8"))
|
|
110
|
+
dest = paths["packs"] / spec["class"]
|
|
111
|
+
if dest.exists():
|
|
112
|
+
out.append(f"Previous pack moved to {_backup(dest, paths)}")
|
|
113
|
+
shutil.copytree(pack_folder, dest)
|
|
114
|
+
out.append(f"Device pack installed: {dest}")
|
|
115
|
+
out.append(f"DONE. Use {spec['class']}(...) in your upload() programs. Restart PictoBlox/IDLE if they are open.")
|
|
116
|
+
return out
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def installed_packs() -> list[dict]:
|
|
120
|
+
paths = runtime_paths()
|
|
121
|
+
out = []
|
|
122
|
+
if paths["packs"].is_dir():
|
|
123
|
+
for f in sorted(paths["packs"].iterdir()):
|
|
124
|
+
if (f / "pack.json").is_file():
|
|
125
|
+
try:
|
|
126
|
+
spec = json.loads((f / "pack.json").read_text(encoding="utf-8"))
|
|
127
|
+
out.append({"class": spec.get("class", f.name), "title": spec.get("title", ""), "folder": f,
|
|
128
|
+
"libraries": [d.get("library_folder", d.get("name")) for d in spec.get("dependencies", [])]})
|
|
129
|
+
except (OSError, ValueError):
|
|
130
|
+
out.append({"class": f.name, "title": "(unreadable pack.json)", "folder": f, "libraries": []})
|
|
131
|
+
return out
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def remove(class_name: str, *, remove_library: bool = True) -> list[str]:
|
|
135
|
+
paths = runtime_paths()
|
|
136
|
+
packs = installed_packs()
|
|
137
|
+
me = next((p for p in packs if p["class"] == class_name), None)
|
|
138
|
+
if me is None:
|
|
139
|
+
raise RuntimeError(f"No added pack named {class_name} (built-in packs cannot be removed here).")
|
|
140
|
+
out = [f"Pack moved to {_backup(me['folder'], paths)}"]
|
|
141
|
+
if remove_library:
|
|
142
|
+
others = {lib for p in packs if p is not me for lib in p["libraries"]}
|
|
143
|
+
for lib in me["libraries"]:
|
|
144
|
+
folder = paths["libraries"] / str(lib)
|
|
145
|
+
if lib in others:
|
|
146
|
+
out.append(f"Library {lib} kept (used by another pack)."); continue
|
|
147
|
+
if folder.is_dir() and (folder / ADDED_MARKER).exists():
|
|
148
|
+
out.append(f"Library moved to {_backup(folder, paths)}")
|
|
149
|
+
elif folder.is_dir():
|
|
150
|
+
out.append(f"Library {lib} kept (it is part of the BARMAJA installation).")
|
|
151
|
+
return out
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ---------------- checking (fresh Python, like a real program run) ----------------
|
|
155
|
+
def check(pack_folder: Path, library_root: Path, *, compile: bool = True) -> tuple[bool, list[str]]:
|
|
156
|
+
spec = json.loads((pack_folder / "pack.json").read_text(encoding="utf-8"))
|
|
157
|
+
holder = Path(tempfile.mkdtemp(prefix="barmaja_check_"))
|
|
158
|
+
(holder / "packs").mkdir(); (holder / "libs").mkdir()
|
|
159
|
+
shutil.copytree(pack_folder, holder / "packs" / pack_folder.name)
|
|
160
|
+
shutil.copytree(library_root, holder / "libs" / library_root.name,
|
|
161
|
+
ignore=shutil.ignore_patterns(".*", "examples", "extras", "test", "tests", "docs"))
|
|
162
|
+
env = {**os.environ, "BARMAJA_DEVICE_PACKS": str(holder / "packs"), "BARMAJA_DEV_LIBRARIES": str(holder / "libs"),
|
|
163
|
+
"BARMAJA_SKIP_BUILTIN_PACKS": spec["class"]}
|
|
164
|
+
code = textwrap.dedent(f'''
|
|
165
|
+
import sys; sys.path.insert(0, {PKG_PARENT!r})
|
|
166
|
+
from Barmaja_PyArduino import device_packs as dp
|
|
167
|
+
from Barmaja_PyArduino.compiler import Transpiler
|
|
168
|
+
from Barmaja_PyArduino.errors import BarmajaError, format_error
|
|
169
|
+
cls = {spec["class"]!r}
|
|
170
|
+
if cls not in dp.LOADED:
|
|
171
|
+
print("FAIL The pack was not accepted:", "; ".join(dp.LOAD_ERRORS)); sys.exit(1)
|
|
172
|
+
print("PASS pack.json accepted")
|
|
173
|
+
try:
|
|
174
|
+
r = Transpiler().compile_text(open({str(pack_folder / "example.py")!r}, encoding="utf-8").read())
|
|
175
|
+
except BarmajaError as e:
|
|
176
|
+
print("FAIL Example program:", " ".join(format_error(e).split())); sys.exit(1)
|
|
177
|
+
print("PASS Example converted to Arduino C++")
|
|
178
|
+
if {compile!r}:
|
|
179
|
+
try:
|
|
180
|
+
from Barmaja_PyArduino.toolchain import compile_sketch
|
|
181
|
+
art = compile_sketch(r.cpp)
|
|
182
|
+
print("PASS Arduino Uno compile OK:", art.memory)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
text = " ".join(str(e).split())
|
|
185
|
+
low = text.lower()
|
|
186
|
+
if "compilation environment was not found" in low or "toolchain not found" in low or ("arduino-cli" in low and "not found" in low):
|
|
187
|
+
print("SKIP Arduino toolchain not found on this PC - compile test skipped:", text[:300])
|
|
188
|
+
else:
|
|
189
|
+
print("FAIL Arduino Uno compile:", text[:900]); sys.exit(1)
|
|
190
|
+
''')
|
|
191
|
+
try:
|
|
192
|
+
res = subprocess.run([sys.executable, "-I", "-c", code], env=env, text=True, capture_output=True, timeout=600)
|
|
193
|
+
finally:
|
|
194
|
+
shutil.rmtree(holder, ignore_errors=True)
|
|
195
|
+
lines = [l for l in (res.stdout + ("\n" + res.stderr.strip()[-1200:] if res.returncode and res.stderr.strip() else "")).splitlines() if l.strip()]
|
|
196
|
+
return res.returncode == 0, lines
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def add_library_auto(path: str, *, cls: str | None = None, student: str | None = None,
|
|
200
|
+
compile: bool = True, do_install: bool = True, say=print) -> int:
|
|
201
|
+
root = pb.open_library(path)
|
|
202
|
+
lib = pb.scan_library(root)
|
|
203
|
+
say(f"Library: {lib.name} {lib.version} licence: {lib.license}")
|
|
204
|
+
if lib.license_warning:
|
|
205
|
+
say("NOTE " + lib.license_warning)
|
|
206
|
+
for p in lib.problems:
|
|
207
|
+
say("NOTE " + p)
|
|
208
|
+
missing = missing_dependencies(lib)
|
|
209
|
+
if lib.depends:
|
|
210
|
+
say("Needs also: " + ", ".join(lib.depends) + (f" MISSING: {', '.join(missing)} - add those libraries first" if missing else " (all found)"))
|
|
211
|
+
cls = cls or pb.main_class(lib)
|
|
212
|
+
if not cls or cls not in lib.classes:
|
|
213
|
+
say(f"FAIL Class not found. Classes: {', '.join(lib.classes) or 'none'}"); return 1
|
|
214
|
+
student = student or pb.default_student_name(cls)
|
|
215
|
+
spec, adapter, example = pb.build_pack(lib, cls, student, ctor_index=-1)
|
|
216
|
+
say(f"Class {cls} -> student class {student} with {len(spec['methods'])} methods")
|
|
217
|
+
if lib.classes[cls].skipped:
|
|
218
|
+
say(f"NOTE {len(lib.classes[cls].skipped)} method(s) not converted (see notes in pack.json)")
|
|
219
|
+
work = Path(tempfile.mkdtemp(prefix="barmaja_pack_"))
|
|
220
|
+
folder = pb.write_pack(work, spec, adapter)
|
|
221
|
+
ok, lines = check(folder, root, compile=compile)
|
|
222
|
+
for line in lines:
|
|
223
|
+
say(line)
|
|
224
|
+
if not ok:
|
|
225
|
+
say(f"Pack NOT installed. Draft kept for editing: {folder}"); return 1
|
|
226
|
+
compiled = any("Arduino Uno compile OK" in l for l in lines)
|
|
227
|
+
if do_install and missing and not compiled:
|
|
228
|
+
say("NOT INSTALLED: add the missing libraries first (add-library <zip> --auto works for them too), then run again.")
|
|
229
|
+
say(f"Draft kept: {folder}"); return 1
|
|
230
|
+
if do_install:
|
|
231
|
+
for line in install(lib, folder):
|
|
232
|
+
say(line)
|
|
233
|
+
else:
|
|
234
|
+
say(f"Checked only (not installed). Pack folder: {folder}")
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ---------------- window ----------------
|
|
239
|
+
def run_gui(initial: str | None = None) -> int:
|
|
240
|
+
import tkinter as tk
|
|
241
|
+
from tkinter import filedialog, messagebox, ttk
|
|
242
|
+
|
|
243
|
+
root = tk.Tk()
|
|
244
|
+
root.title("BARMAJA Library Adder - add an Arduino library")
|
|
245
|
+
root.geometry("1080x740")
|
|
246
|
+
try:
|
|
247
|
+
ico = Path(__file__).resolve().parent / "BARMAJA_PyArduino.ico"
|
|
248
|
+
if ico.is_file() and os.name == "nt":
|
|
249
|
+
root.iconbitmap(str(ico))
|
|
250
|
+
except Exception:
|
|
251
|
+
pass
|
|
252
|
+
state: dict = {"lib": None, "folder": None}
|
|
253
|
+
|
|
254
|
+
top = ttk.Frame(root, padding=8); top.pack(fill="x")
|
|
255
|
+
ttk.Label(top, text="1. Arduino library:", font=("Segoe UI", 10, "bold")).pack(side="left")
|
|
256
|
+
path_var = tk.StringVar(value="Choose a library ZIP (e.g. downloaded from GitHub) or its folder")
|
|
257
|
+
ttk.Button(top, text="Choose ZIP...", command=lambda: load(filedialog.askopenfilename(filetypes=[("Arduino library ZIP", "*.zip")]))).pack(side="left", padx=4)
|
|
258
|
+
ttk.Button(top, text="Choose folder...", command=lambda: load(filedialog.askdirectory())).pack(side="left")
|
|
259
|
+
ttk.Label(top, textvariable=path_var, foreground="#555").pack(side="left", padx=8)
|
|
260
|
+
|
|
261
|
+
info_var = tk.StringVar(value="")
|
|
262
|
+
ttk.Label(root, textvariable=info_var, padding=(8, 0)).pack(fill="x")
|
|
263
|
+
|
|
264
|
+
mid = ttk.Frame(root, padding=8); mid.pack(fill="x")
|
|
265
|
+
ttk.Label(mid, text="2. Library class:").grid(row=0, column=0, sticky="w")
|
|
266
|
+
class_var = tk.StringVar(); class_box = ttk.Combobox(mid, textvariable=class_var, state="readonly", width=34)
|
|
267
|
+
class_box.grid(row=0, column=1, sticky="w", padx=4)
|
|
268
|
+
ttk.Label(mid, text="Name for students:").grid(row=0, column=2, sticky="w", padx=(16, 0))
|
|
269
|
+
student_var = tk.StringVar(); ttk.Entry(mid, textvariable=student_var, width=26).grid(row=0, column=3, sticky="w", padx=4)
|
|
270
|
+
ttk.Label(mid, text="Constructor:").grid(row=1, column=0, sticky="w", pady=4)
|
|
271
|
+
ctor_var = tk.StringVar(); ctor_box = ttk.Combobox(mid, textvariable=ctor_var, state="readonly", width=60)
|
|
272
|
+
ctor_box.grid(row=1, column=1, columnspan=3, sticky="w", padx=4)
|
|
273
|
+
print_var = tk.BooleanVar(value=False)
|
|
274
|
+
print_chk = ttk.Checkbutton(mid, text="Add print()/println() (screens, displays)", variable=print_var)
|
|
275
|
+
print_chk.grid(row=2, column=1, columnspan=3, sticky="w", padx=4)
|
|
276
|
+
|
|
277
|
+
ttk.Label(root, text="3. Methods students can use (click a row to tick/untick; double-click the name to rename):", padding=(8, 2)).pack(fill="x")
|
|
278
|
+
cols = ("use", "student", "library", "params", "returns")
|
|
279
|
+
tree = ttk.Treeview(root, columns=cols, show="headings", height=13)
|
|
280
|
+
for c, w, t in [("use", 50, "Use"), ("student", 170, "Student name"), ("library", 170, "Library method"), ("params", 420, "Parameters (* = pin)"), ("returns", 80, "Returns")]:
|
|
281
|
+
tree.heading(c, text=t); tree.column(c, width=w, anchor="w")
|
|
282
|
+
tree.pack(fill="both", expand=False, padx=8)
|
|
283
|
+
|
|
284
|
+
btns = ttk.Frame(root, padding=8); btns.pack(fill="x")
|
|
285
|
+
log = tk.Text(root, height=12, wrap="word", font=("Consolas", 9)); log.pack(fill="both", expand=True, padx=8, pady=(0, 8))
|
|
286
|
+
|
|
287
|
+
def say(text: str) -> None:
|
|
288
|
+
def put():
|
|
289
|
+
log.insert("end", text + "\n"); log.see("end")
|
|
290
|
+
root.after(0, put)
|
|
291
|
+
|
|
292
|
+
def current_scan():
|
|
293
|
+
lib = state["lib"]
|
|
294
|
+
return lib.classes.get(class_var.get()) if lib else None
|
|
295
|
+
|
|
296
|
+
def fill_methods(*_):
|
|
297
|
+
scan = current_scan()
|
|
298
|
+
tree.delete(*tree.get_children())
|
|
299
|
+
if not scan:
|
|
300
|
+
return
|
|
301
|
+
student_var.set(pb.default_student_name(scan.name))
|
|
302
|
+
ctors = [", ".join(p.name + ("*" if p.is_pin else "") + (f"={p.default}" if p.default is not None else "") for p in c) or "(no arguments)" for c in scan.constructors]
|
|
303
|
+
ctor_box["values"] = ctors
|
|
304
|
+
ctor_box.current(pb.default_constructor(scan))
|
|
305
|
+
print_var.set(scan.printable)
|
|
306
|
+
for i, m in enumerate(scan.methods):
|
|
307
|
+
ps = ", ".join(f"{p.name}{'*' if p.is_pin else ''}: {p.type}" + (f"={p.default}" if p.default is not None else "") for p in m.params)
|
|
308
|
+
tree.insert("", "end", iid=str(i), values=("[x]" if m.selected else "[ ]", m.student_name, m.cpp_name, ps, m.returns))
|
|
309
|
+
if scan.skipped:
|
|
310
|
+
say(f"{len(scan.skipped)} method(s) cannot be converted automatically (arrays, pointers, objects):")
|
|
311
|
+
for s in scan.skipped[:12]:
|
|
312
|
+
say(" - " + s)
|
|
313
|
+
|
|
314
|
+
def toggle(event):
|
|
315
|
+
row = tree.identify_row(event.y); col = tree.identify_column(event.x)
|
|
316
|
+
scan = current_scan()
|
|
317
|
+
if not row or not scan or col == "#2":
|
|
318
|
+
return
|
|
319
|
+
m = scan.methods[int(row)]; m.selected = not m.selected
|
|
320
|
+
tree.set(row, "use", "[x]" if m.selected else "[ ]")
|
|
321
|
+
|
|
322
|
+
def rename(event):
|
|
323
|
+
row = tree.identify_row(event.y); scan = current_scan()
|
|
324
|
+
if not row or not scan or tree.identify_column(event.x) != "#2":
|
|
325
|
+
return
|
|
326
|
+
x, y, w, h = tree.bbox(row, "student")
|
|
327
|
+
entry = ttk.Entry(tree); entry.place(x=x, y=y, width=w, height=h)
|
|
328
|
+
entry.insert(0, scan.methods[int(row)].student_name); entry.focus()
|
|
329
|
+
def done(_=None):
|
|
330
|
+
new = entry.get().strip()
|
|
331
|
+
if new:
|
|
332
|
+
scan.methods[int(row)].student_name = new; tree.set(row, "student", new)
|
|
333
|
+
entry.destroy()
|
|
334
|
+
entry.bind("<Return>", done); entry.bind("<FocusOut>", done)
|
|
335
|
+
|
|
336
|
+
tree.bind("<Button-1>", toggle); tree.bind("<Double-1>", rename)
|
|
337
|
+
class_box.bind("<<ComboboxSelected>>", fill_methods)
|
|
338
|
+
|
|
339
|
+
def load(path):
|
|
340
|
+
if not path:
|
|
341
|
+
return
|
|
342
|
+
try:
|
|
343
|
+
libroot = pb.open_library(path)
|
|
344
|
+
lib = pb.scan_library(libroot)
|
|
345
|
+
except Exception as exc:
|
|
346
|
+
messagebox.showerror("Library Adder", str(exc)); return
|
|
347
|
+
state["lib"] = lib; path_var.set(str(path))
|
|
348
|
+
info_var.set(f"{lib.name} version {lib.version} licence {lib.license}" + (f" ⚠ {lib.license_warning}" if lib.license_warning else ""))
|
|
349
|
+
log.delete("1.0", "end")
|
|
350
|
+
if lib.depends:
|
|
351
|
+
missing = missing_dependencies(lib)
|
|
352
|
+
say("This library also needs: " + ", ".join(lib.depends))
|
|
353
|
+
say((" MISSING on this PC: " + ", ".join(missing) + " -> open each one here and press 'Add library files only' first.")
|
|
354
|
+
if missing else " All of them are already installed.")
|
|
355
|
+
for p in lib.problems:
|
|
356
|
+
say("PROBLEM: " + p)
|
|
357
|
+
class_box["values"] = list(lib.classes)
|
|
358
|
+
main = pb.main_class(lib)
|
|
359
|
+
if main:
|
|
360
|
+
class_var.set(main); fill_methods()
|
|
361
|
+
|
|
362
|
+
def make_pack():
|
|
363
|
+
lib, scan = state["lib"], current_scan()
|
|
364
|
+
if not lib or not scan:
|
|
365
|
+
messagebox.showinfo("Library Adder", "Choose a library first."); return None
|
|
366
|
+
spec, adapter, _ = pb.build_pack(lib, scan.name, student_var.get().strip(), ctor_box.current(), add_print=print_var.get())
|
|
367
|
+
folder = pb.write_pack(Path(tempfile.mkdtemp(prefix="barmaja_pack_")), spec, adapter)
|
|
368
|
+
state["folder"] = folder
|
|
369
|
+
return lib, folder
|
|
370
|
+
|
|
371
|
+
def busy(flag: bool):
|
|
372
|
+
for b in (check_btn, add_btn):
|
|
373
|
+
b.state(["disabled"] if flag else ["!disabled"])
|
|
374
|
+
|
|
375
|
+
def run_check(then_install: bool):
|
|
376
|
+
try:
|
|
377
|
+
made = make_pack()
|
|
378
|
+
except ValueError as exc:
|
|
379
|
+
messagebox.showerror("Library Adder", str(exc)); return
|
|
380
|
+
if not made:
|
|
381
|
+
return
|
|
382
|
+
lib, folder = made
|
|
383
|
+
say("\nChecking... (converting the example and compiling it for Arduino Uno; this can take a minute)")
|
|
384
|
+
busy(True)
|
|
385
|
+
def work():
|
|
386
|
+
ok, lines = check(folder, lib.root, compile=True)
|
|
387
|
+
for l in lines:
|
|
388
|
+
say(l)
|
|
389
|
+
missing = missing_dependencies(lib)
|
|
390
|
+
compiled = any("Arduino Uno compile OK" in l for l in lines)
|
|
391
|
+
if ok and then_install and missing and not compiled:
|
|
392
|
+
say("NOT INSTALLED: first add the missing libraries: " + ", ".join(missing))
|
|
393
|
+
elif ok and then_install:
|
|
394
|
+
try:
|
|
395
|
+
for l in install(lib, folder):
|
|
396
|
+
say(l)
|
|
397
|
+
root.after(0, lambda: messagebox.showinfo("Library Adder", f"{student_var.get()} was added to BARMAJA PyArduino."))
|
|
398
|
+
except Exception as exc:
|
|
399
|
+
say(f"NOT INSTALLED: {exc}")
|
|
400
|
+
elif not ok:
|
|
401
|
+
say("Fix: untick the method named in the error (or rename it), then press Check again.")
|
|
402
|
+
say("Example program for students:\n" + (folder / "example.py").read_text(encoding="utf-8"))
|
|
403
|
+
root.after(0, lambda: busy(False))
|
|
404
|
+
threading.Thread(target=work, daemon=True).start()
|
|
405
|
+
|
|
406
|
+
def manage():
|
|
407
|
+
win = tk.Toplevel(root); win.title("Libraries added to BARMAJA"); win.geometry("640x320")
|
|
408
|
+
lb = tk.Listbox(win); lb.pack(fill="both", expand=True, padx=8, pady=8)
|
|
409
|
+
items = installed_packs()
|
|
410
|
+
for p in items:
|
|
411
|
+
lb.insert("end", f"{p['class']} - {p['title']}")
|
|
412
|
+
def do_remove():
|
|
413
|
+
sel = lb.curselection()
|
|
414
|
+
if not sel:
|
|
415
|
+
return
|
|
416
|
+
name = items[sel[0]]["class"]
|
|
417
|
+
if messagebox.askyesno("Remove", f"Remove {name}? (moved to the backups folder, not deleted)"):
|
|
418
|
+
for l in remove(name):
|
|
419
|
+
say(l)
|
|
420
|
+
win.destroy()
|
|
421
|
+
ttk.Button(win, text="Remove selected", command=do_remove).pack(pady=(0, 8))
|
|
422
|
+
|
|
423
|
+
check_btn = ttk.Button(btns, text="4. Check (convert + compile test)", command=lambda: run_check(False)); check_btn.pack(side="left")
|
|
424
|
+
add_btn = ttk.Button(btns, text="5. Add to BARMAJA", command=lambda: run_check(True)); add_btn.pack(side="left", padx=8)
|
|
425
|
+
def library_only():
|
|
426
|
+
lib = state["lib"]
|
|
427
|
+
if not lib:
|
|
428
|
+
messagebox.showinfo("Library Adder", "Choose a library first."); return
|
|
429
|
+
try:
|
|
430
|
+
for l in install_library_only(lib):
|
|
431
|
+
say(l)
|
|
432
|
+
except Exception as exc:
|
|
433
|
+
say(f"NOT INSTALLED: {exc}")
|
|
434
|
+
ttk.Button(btns, text="Add library files only", command=library_only).pack(side="left")
|
|
435
|
+
ttk.Button(btns, text="Added libraries / Remove...", command=manage).pack(side="left", padx=8)
|
|
436
|
+
ttk.Label(btns, text="upload() only • offline • old files kept in backups", foreground="#666").pack(side="right")
|
|
437
|
+
if initial:
|
|
438
|
+
root.after(100, lambda: load(initial))
|
|
439
|
+
root.mainloop()
|
|
440
|
+
return 0
|