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.
Files changed (87) hide show
  1. Barmaja_PyArduino/BARMAJA_PyArduino.ico +0 -0
  2. Barmaja_PyArduino/__init__.py +528 -0
  3. Barmaja_PyArduino/__init__.pyi +331 -0
  4. Barmaja_PyArduino/__main__.py +2 -0
  5. Barmaja_PyArduino/adapters/__init__.py +35 -0
  6. Barmaja_PyArduino/api_registry.py +459 -0
  7. Barmaja_PyArduino/backend/__init__.py +3 -0
  8. Barmaja_PyArduino/backend/uno_cpp.py +1680 -0
  9. Barmaja_PyArduino/board.py +80 -0
  10. Barmaja_PyArduino/cli.py +223 -0
  11. Barmaja_PyArduino/compiler.py +1721 -0
  12. Barmaja_PyArduino/console.py +217 -0
  13. Barmaja_PyArduino/device_packs.py +367 -0
  14. Barmaja_PyArduino/device_packs_builtin/Button/arduino/Barmaja_Button/library.properties +9 -0
  15. Barmaja_PyArduino/device_packs_builtin/Button/arduino/Barmaja_Button/src/Barmaja_Button.h +18 -0
  16. Barmaja_PyArduino/device_packs_builtin/Button/example.py +10 -0
  17. Barmaja_PyArduino/device_packs_builtin/Button/pack.json +40 -0
  18. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/arduino/Barmaja_ClockDS3231/library.properties +9 -0
  19. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/arduino/Barmaja_ClockDS3231/src/Barmaja_ClockDS3231.h +20 -0
  20. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/example.py +8 -0
  21. Barmaja_PyArduino/device_packs_builtin/ClockDS3231/pack.json +136 -0
  22. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/arduino/Barmaja_DistanceVL53L0X/library.properties +9 -0
  23. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/arduino/Barmaja_DistanceVL53L0X/src/Barmaja_DistanceVL53L0X.h +12 -0
  24. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/example.py +8 -0
  25. Barmaja_PyArduino/device_packs_builtin/DistanceVL53L0X/pack.json +48 -0
  26. Barmaja_PyArduino/device_packs_builtin/LCDParallel/arduino/Barmaja_LCDParallel/library.properties +9 -0
  27. Barmaja_PyArduino/device_packs_builtin/LCDParallel/arduino/Barmaja_LCDParallel/src/Barmaja_LCDParallel.h +17 -0
  28. Barmaja_PyArduino/device_packs_builtin/LCDParallel/example.py +10 -0
  29. Barmaja_PyArduino/device_packs_builtin/LCDParallel/pack.json +137 -0
  30. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/arduino/Barmaja_LoadCellHX711/library.properties +9 -0
  31. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/arduino/Barmaja_LoadCellHX711/src/Barmaja_LoadCellHX711.h +16 -0
  32. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/example.py +11 -0
  33. Barmaja_PyArduino/device_packs_builtin/LoadCellHX711/pack.json +74 -0
  34. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/arduino/Barmaja_NeoPixelStrip/library.properties +9 -0
  35. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/arduino/Barmaja_NeoPixelStrip/src/Barmaja_NeoPixelStrip.h +15 -0
  36. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/example.py +13 -0
  37. Barmaja_PyArduino/device_packs_builtin/NeoPixelStrip/pack.json +114 -0
  38. Barmaja_PyArduino/device_packs_builtin/OLEDText/arduino/Barmaja_OLEDText/library.properties +9 -0
  39. Barmaja_PyArduino/device_packs_builtin/OLEDText/arduino/Barmaja_OLEDText/src/Barmaja_OLEDText.h +21 -0
  40. Barmaja_PyArduino/device_packs_builtin/OLEDText/example.py +9 -0
  41. Barmaja_PyArduino/device_packs_builtin/OLEDText/pack.json +103 -0
  42. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/arduino/Barmaja_PressureBMP280/library.properties +9 -0
  43. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/arduino/Barmaja_PressureBMP280/src/Barmaja_PressureBMP280.h +16 -0
  44. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/example.py +8 -0
  45. Barmaja_PyArduino/device_packs_builtin/PressureBMP280/pack.json +92 -0
  46. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/arduino/Barmaja_RotaryEncoder/library.properties +9 -0
  47. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/arduino/Barmaja_RotaryEncoder/src/Barmaja_RotaryEncoder.h +18 -0
  48. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/example.py +10 -0
  49. Barmaja_PyArduino/device_packs_builtin/RotaryEncoder/pack.json +44 -0
  50. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/arduino/Barmaja_ServoDriverPCA9685/library.properties +9 -0
  51. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/arduino/Barmaja_ServoDriverPCA9685/src/Barmaja_ServoDriverPCA9685.h +15 -0
  52. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/example.py +10 -0
  53. Barmaja_PyArduino/device_packs_builtin/ServoDriverPCA9685/pack.json +117 -0
  54. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/arduino/Barmaja_ShiftRegister595/library.properties +9 -0
  55. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/arduino/Barmaja_ShiftRegister595/src/Barmaja_ShiftRegister595.h +14 -0
  56. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/example.py +9 -0
  57. Barmaja_PyArduino/device_packs_builtin/ShiftRegister595/pack.json +83 -0
  58. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/arduino/Barmaja_TemperatureDS18B20/library.properties +9 -0
  59. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/arduino/Barmaja_TemperatureDS18B20/src/Barmaja_TemperatureDS18B20.h +16 -0
  60. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/example.py +8 -0
  61. Barmaja_PyArduino/device_packs_builtin/TemperatureDS18B20/pack.json +63 -0
  62. Barmaja_PyArduino/doctor.py +406 -0
  63. Barmaja_PyArduino/environment.py +595 -0
  64. Barmaja_PyArduino/errors.py +166 -0
  65. Barmaja_PyArduino/flow.py +754 -0
  66. Barmaja_PyArduino/frontend.py +611 -0
  67. Barmaja_PyArduino/installer.py +151 -0
  68. Barmaja_PyArduino/ir.py +660 -0
  69. Barmaja_PyArduino/library_adder.py +440 -0
  70. Barmaja_PyArduino/live.py +859 -0
  71. Barmaja_PyArduino/lowering.py +596 -0
  72. Barmaja_PyArduino/numeric.py +748 -0
  73. Barmaja_PyArduino/pack_builder.py +544 -0
  74. Barmaja_PyArduino/py.typed +0 -0
  75. Barmaja_PyArduino/serial_monitor.py +172 -0
  76. Barmaja_PyArduino/source.py +184 -0
  77. Barmaja_PyArduino/stubgen.py +148 -0
  78. Barmaja_PyArduino/symbols.py +646 -0
  79. Barmaja_PyArduino/target.py +1631 -0
  80. Barmaja_PyArduino/toolchain.py +506 -0
  81. Barmaja_PyArduino/typesys.py +917 -0
  82. Barmaja_PyArduino/workflow.py +213 -0
  83. barmaja_pyarduino-2.0.0.dist-info/METADATA +39 -0
  84. barmaja_pyarduino-2.0.0.dist-info/RECORD +87 -0
  85. barmaja_pyarduino-2.0.0.dist-info/WHEEL +5 -0
  86. barmaja_pyarduino-2.0.0.dist-info/licenses/LICENSE.txt +7 -0
  87. 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