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,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"class": "ShiftRegister595",
|
|
4
|
+
"title": "74HC595 shift register (8 extra outputs from 3 pins)",
|
|
5
|
+
"status": "compile-verified",
|
|
6
|
+
"boards": [
|
|
7
|
+
"uno",
|
|
8
|
+
"nano"
|
|
9
|
+
],
|
|
10
|
+
"constructor": [
|
|
11
|
+
{
|
|
12
|
+
"name": "data_pin",
|
|
13
|
+
"type": "Int",
|
|
14
|
+
"rule": "digital_pin"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "clock_pin",
|
|
18
|
+
"type": "Int",
|
|
19
|
+
"rule": "digital_pin"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "latch_pin",
|
|
23
|
+
"type": "Int",
|
|
24
|
+
"rule": "digital_pin"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"methods": {
|
|
28
|
+
"write": {
|
|
29
|
+
"params": [
|
|
30
|
+
{
|
|
31
|
+
"name": "value",
|
|
32
|
+
"type": "Int",
|
|
33
|
+
"range": [
|
|
34
|
+
0,
|
|
35
|
+
255
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"returns": "Void",
|
|
40
|
+
"requires_begin": false
|
|
41
|
+
},
|
|
42
|
+
"setPin": {
|
|
43
|
+
"params": [
|
|
44
|
+
{
|
|
45
|
+
"name": "output",
|
|
46
|
+
"type": "Int",
|
|
47
|
+
"range": [
|
|
48
|
+
0,
|
|
49
|
+
7
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "on",
|
|
54
|
+
"type": "Bool"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"returns": "Void",
|
|
58
|
+
"requires_begin": false
|
|
59
|
+
},
|
|
60
|
+
"clear": {
|
|
61
|
+
"params": [],
|
|
62
|
+
"returns": "Void",
|
|
63
|
+
"requires_begin": false
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"resources": [],
|
|
67
|
+
"warnings": [],
|
|
68
|
+
"adapter": {
|
|
69
|
+
"library": "Barmaja_ShiftRegister595",
|
|
70
|
+
"header": "Barmaja_ShiftRegister595.h"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": [],
|
|
73
|
+
"liverun": false,
|
|
74
|
+
"example": "from Barmaja_PyArduino import *\nupload()\n\nleds = ShiftRegister595(11, 12, 8)\nwhile True:\n for k in range(8):\n leds.clear()\n leds.setPin(k, True)\n delay(150)\n",
|
|
75
|
+
"pins": {
|
|
76
|
+
"forbid": [
|
|
77
|
+
0,
|
|
78
|
+
1
|
|
79
|
+
],
|
|
80
|
+
"distinct": true
|
|
81
|
+
},
|
|
82
|
+
"notes": "No library needed."
|
|
83
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <Arduino.h>
|
|
3
|
+
#include <OneWire.h>
|
|
4
|
+
#include <DallasTemperature.h>
|
|
5
|
+
// Data pin needs a 4.7k pull-up resistor to 5V. Reads the first sensor on the bus.
|
|
6
|
+
// Returns -127.0 when no sensor answers.
|
|
7
|
+
class TemperatureDS18B20 {
|
|
8
|
+
public:
|
|
9
|
+
explicit TemperatureDS18B20(uint8_t pin): wire_(pin), sensors_(&wire_) {}
|
|
10
|
+
void begin() { sensors_.begin(); }
|
|
11
|
+
float readCelsius() { sensors_.requestTemperatures(); return sensors_.getTempCByIndex(0); }
|
|
12
|
+
float readFahrenheit() { sensors_.requestTemperatures(); return sensors_.getTempFByIndex(0); }
|
|
13
|
+
private:
|
|
14
|
+
OneWire wire_;
|
|
15
|
+
DallasTemperature sensors_;
|
|
16
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"class": "TemperatureDS18B20",
|
|
4
|
+
"title": "DS18B20 digital temperature sensor (one-wire)",
|
|
5
|
+
"status": "compile-verified",
|
|
6
|
+
"boards": [
|
|
7
|
+
"uno",
|
|
8
|
+
"nano"
|
|
9
|
+
],
|
|
10
|
+
"constructor": [
|
|
11
|
+
{
|
|
12
|
+
"name": "pin",
|
|
13
|
+
"type": "Int",
|
|
14
|
+
"rule": "digital_pin"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"methods": {
|
|
18
|
+
"begin": {
|
|
19
|
+
"params": [],
|
|
20
|
+
"returns": "Void",
|
|
21
|
+
"starts": true
|
|
22
|
+
},
|
|
23
|
+
"readCelsius": {
|
|
24
|
+
"params": [],
|
|
25
|
+
"returns": "Float"
|
|
26
|
+
},
|
|
27
|
+
"readFahrenheit": {
|
|
28
|
+
"params": [],
|
|
29
|
+
"returns": "Float"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"resources": [],
|
|
33
|
+
"warnings": [],
|
|
34
|
+
"adapter": {
|
|
35
|
+
"library": "Barmaja_TemperatureDS18B20",
|
|
36
|
+
"header": "Barmaja_TemperatureDS18B20.h"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": [
|
|
39
|
+
{
|
|
40
|
+
"id": "paulstoffregen-onewire",
|
|
41
|
+
"name": "OneWire",
|
|
42
|
+
"version": "2.3.8",
|
|
43
|
+
"header": "OneWire.h",
|
|
44
|
+
"license": "MIT-style (PJRC)",
|
|
45
|
+
"source": "https://github.com/PaulStoffregen/OneWire",
|
|
46
|
+
"library_folder": "OneWire",
|
|
47
|
+
"bundled": true
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "milesburton-dallastemperature",
|
|
51
|
+
"name": "DallasTemperature",
|
|
52
|
+
"version": "4.0.6",
|
|
53
|
+
"header": "DallasTemperature.h",
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"source": "https://github.com/milesburton/Arduino-Temperature-Control-Library",
|
|
56
|
+
"tag": "v4.0.6",
|
|
57
|
+
"library_folder": "DallasTemperature"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"liverun": false,
|
|
61
|
+
"example": "from Barmaja_PyArduino import *\nupload()\n\ntemp = TemperatureDS18B20(4)\ntemp.begin()\nwhile True:\n print(\"Temperature =\", temp.readCelsius(), \"C\")\n delay(1000)\n",
|
|
62
|
+
"notes": "Each read waits ~750 ms for the sensor conversion."
|
|
63
|
+
}
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .environment import (
|
|
12
|
+
ensure_runtime_dirs, find_normal_arduino_cli, invalidate_cache, load_cache, make_fingerprint,
|
|
13
|
+
pictoblox_module_dirs, run_text, save_cache,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def _cli_cmd(cli: str, *args: str) -> list[str]:
|
|
17
|
+
cfg = os.environ.get("BARMAJA_ARDUINO_CONFIG")
|
|
18
|
+
prefix = [cli]
|
|
19
|
+
if cfg and Path(cfg).exists():
|
|
20
|
+
prefix += ["--config-file", cfg]
|
|
21
|
+
else:
|
|
22
|
+
bundled = ensure_runtime_dirs()["root"] / "runtime" / "arduino-cli.yaml"
|
|
23
|
+
try:
|
|
24
|
+
if bundled.exists() and Path(cli).resolve().parent == bundled.parent.resolve():
|
|
25
|
+
prefix += ["--config-file", str(bundled)]
|
|
26
|
+
except OSError:
|
|
27
|
+
pass
|
|
28
|
+
return [*prefix, *args]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
REQUIRED_LIBRARIES = {
|
|
32
|
+
"Servo": "1.3.0",
|
|
33
|
+
"Ultrasonic": "3.0.0",
|
|
34
|
+
"DHT sensor library": "1.4.7",
|
|
35
|
+
"Adafruit Unified Sensor": "1.1.15",
|
|
36
|
+
"LiquidCrystal I2C": "1.1.4",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class DoctorItem:
|
|
41
|
+
key: str
|
|
42
|
+
label: str
|
|
43
|
+
status: str # OK/WARN/ERROR
|
|
44
|
+
detail: str
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class DoctorReport:
|
|
48
|
+
items: tuple[DoctorItem, ...]
|
|
49
|
+
source: str | None = None
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def ok(self) -> bool:
|
|
53
|
+
return not any(i.status == "ERROR" for i in self.items)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def problems(self) -> tuple[DoctorItem, ...]:
|
|
57
|
+
return tuple(i for i in self.items if i.status != "OK")
|
|
58
|
+
|
|
59
|
+
def concise(self) -> str:
|
|
60
|
+
if self.ok and not self.problems:
|
|
61
|
+
return "ALL OK"
|
|
62
|
+
if self.ok:
|
|
63
|
+
return "\n".join(f"Warning: {i.label}: {i.detail}" for i in self.problems)
|
|
64
|
+
return "\n".join(f"- {i.label}: {i.detail}" for i in self.problems)
|
|
65
|
+
|
|
66
|
+
def detailed(self) -> str:
|
|
67
|
+
lines = []
|
|
68
|
+
for i in self.items:
|
|
69
|
+
lines.append(f"{i.label:<28} {i.status:<5} {i.detail}")
|
|
70
|
+
if self.source:
|
|
71
|
+
lines.append(f"Environment source {self.source}")
|
|
72
|
+
return "\n".join(lines)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _cli_version(cli: str) -> tuple[str | None, DoctorItem]:
|
|
76
|
+
result = run_text(_cli_cmd(cli, "version", "--format", "json"), timeout=8)
|
|
77
|
+
if result.returncode != 0:
|
|
78
|
+
result = run_text(_cli_cmd(cli, "version"), timeout=8)
|
|
79
|
+
text = (result.stdout + "\n" + result.stderr).strip()
|
|
80
|
+
if result.returncode != 0:
|
|
81
|
+
return None, DoctorItem("arduino_cli", "Arduino CLI", "ERROR", "found but could not run")
|
|
82
|
+
m = re.search(r"(?:Version:|version\"\s*:\s*\")?v?(\d+\.\d+\.\d+)", text, re.I)
|
|
83
|
+
version = m.group(1) if m else "unknown"
|
|
84
|
+
return version, DoctorItem("arduino_cli", "Arduino CLI", "OK", version)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _board_core(cli: str) -> tuple[Path | None, DoctorItem]:
|
|
88
|
+
result = run_text(_cli_cmd(cli, "core", "list", "--format", "json"), timeout=12)
|
|
89
|
+
if result.returncode != 0:
|
|
90
|
+
return None, DoctorItem("avr_core", "Arduino Uno AVR Core", "ERROR", "not available")
|
|
91
|
+
try:
|
|
92
|
+
payload = json.loads(result.stdout or "[]")
|
|
93
|
+
except json.JSONDecodeError:
|
|
94
|
+
payload = []
|
|
95
|
+
rows = payload if isinstance(payload, list) else payload.get("platforms", []) if isinstance(payload, dict) else []
|
|
96
|
+
for row in rows:
|
|
97
|
+
ident = row.get("id") or row.get("ID") or row.get("platform")
|
|
98
|
+
ver = row.get("installed_version") or row.get("version") or row.get("Installed")
|
|
99
|
+
if not ver and isinstance(row.get("releases"), dict):
|
|
100
|
+
for rv, meta in row["releases"].items():
|
|
101
|
+
if isinstance(meta, dict) and meta.get("installed") is True:
|
|
102
|
+
ver = rv; break
|
|
103
|
+
if ident == "arduino:avr":
|
|
104
|
+
status = "OK" if str(ver) == "1.8.8" else "WARN"
|
|
105
|
+
return None, DoctorItem("avr_core", "Arduino Uno AVR Core", status, str(ver or "unknown"))
|
|
106
|
+
return None, DoctorItem("avr_core", "Arduino Uno AVR Core", "ERROR", "missing")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _libraries(cli: str) -> tuple[list[Path], list[DoctorItem]]:
|
|
110
|
+
result = run_text(_cli_cmd(cli, "lib", "list", "--format", "json"), timeout=15)
|
|
111
|
+
items: list[DoctorItem] = []
|
|
112
|
+
markers: list[Path] = []
|
|
113
|
+
if result.returncode != 0:
|
|
114
|
+
return markers, [DoctorItem("libraries", "Arduino libraries", "ERROR", "could not query libraries")]
|
|
115
|
+
try:
|
|
116
|
+
payload = json.loads(result.stdout or "[]")
|
|
117
|
+
except json.JSONDecodeError:
|
|
118
|
+
payload = []
|
|
119
|
+
rows = payload if isinstance(payload, list) else payload.get("installed_libraries", []) if isinstance(payload, dict) else []
|
|
120
|
+
available = {}
|
|
121
|
+
aliases = {"liquidcrystal_i2c": "LiquidCrystal I2C", "liquidcrystal i2c": "LiquidCrystal I2C"}
|
|
122
|
+
for row in rows:
|
|
123
|
+
lib = row.get("library", row) if isinstance(row, dict) else {}
|
|
124
|
+
name = lib.get("name")
|
|
125
|
+
version = lib.get("version")
|
|
126
|
+
install_dir = lib.get("install_dir")
|
|
127
|
+
if name:
|
|
128
|
+
canonical = aliases.get(str(name).lower(), name)
|
|
129
|
+
available[canonical] = (str(version or "unknown"), Path(install_dir) if install_dir else None)
|
|
130
|
+
if install_dir:
|
|
131
|
+
markers.append(Path(install_dir) / "library.properties")
|
|
132
|
+
# Merge Barmaja-private Light/Full-pack libraries; these intentionally take precedence.
|
|
133
|
+
private = ensure_runtime_dirs()["root"] / "arduino-libraries"
|
|
134
|
+
if private.exists():
|
|
135
|
+
for props in private.glob("*/library.properties"):
|
|
136
|
+
data={}
|
|
137
|
+
for line in props.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
138
|
+
if "=" in line:
|
|
139
|
+
k,v=line.split("=",1); data[k.strip()]=v.strip()
|
|
140
|
+
n=data.get("name"); v=data.get("version","unknown")
|
|
141
|
+
if n:
|
|
142
|
+
canonical=aliases.get(n.lower(), n); available[canonical]=(v, props.parent); markers.append(props)
|
|
143
|
+
for name, expected in REQUIRED_LIBRARIES.items():
|
|
144
|
+
entry = available.get(name)
|
|
145
|
+
got = entry[0] if entry else None
|
|
146
|
+
if got is None:
|
|
147
|
+
items.append(DoctorItem(f"lib:{name}", name, "ERROR", f"missing (need {expected})"))
|
|
148
|
+
elif got == expected:
|
|
149
|
+
items.append(DoctorItem(f"lib:{name}", name, "OK", got))
|
|
150
|
+
else:
|
|
151
|
+
items.append(DoctorItem(f"lib:{name}", name, "WARN", f"{got}; tested version is {expected}"))
|
|
152
|
+
return markers, items
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _compile_capability(cli: str, verbose: bool = False) -> DoctorItem:
|
|
156
|
+
"""Real Uno test compile with the same library pinning as student builds.
|
|
157
|
+
|
|
158
|
+
The sketch lives under the ASCII-only Barmaja runtime root (not %TEMP%, which
|
|
159
|
+
contains the Windows user name) so non-ASCII user names do not break the test.
|
|
160
|
+
"""
|
|
161
|
+
import tempfile
|
|
162
|
+
from .toolchain import _private_library_args
|
|
163
|
+
build_root = ensure_runtime_dirs()["build"]
|
|
164
|
+
with tempfile.TemporaryDirectory(prefix="doctor_", dir=str(build_root)) as td:
|
|
165
|
+
root = Path(td); sketch = root / "DoctorBlink"; sketch.mkdir()
|
|
166
|
+
(sketch / "DoctorBlink.ino").write_text(
|
|
167
|
+
"void setup(){pinMode(13,OUTPUT);}\nvoid loop(){digitalWrite(13,HIGH);delay(10);digitalWrite(13,LOW);delay(10);}\n",
|
|
168
|
+
encoding="utf-8",
|
|
169
|
+
)
|
|
170
|
+
args = _cli_cmd(cli, "compile", "--fqbn", "arduino:avr:uno", "--warnings", "all")
|
|
171
|
+
args += _private_library_args()
|
|
172
|
+
args += ["--build-path", str(root / "build"), str(sketch)]
|
|
173
|
+
result = run_text(args, timeout=180)
|
|
174
|
+
if result.returncode == 0:
|
|
175
|
+
return DoctorItem("compile_test", "Uno test compile", "OK", "PASS")
|
|
176
|
+
if verbose:
|
|
177
|
+
tail = " | ".join(line.strip() for line in ((result.stdout or "") + "\n" + (result.stderr or "")).strip().splitlines()[-6:])
|
|
178
|
+
return DoctorItem("compile_test", "Uno test compile", "ERROR", f"failed: {tail[:900]}")
|
|
179
|
+
return DoctorItem("compile_test", "Uno test compile", "ERROR", "failed; run 'pyarduino doctor --details' for the compiler output")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _running_in_pictoblox() -> bool:
|
|
183
|
+
exe = (sys.executable or "").lower().replace("\\", "/")
|
|
184
|
+
return "pictoblox" in exe
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _pictoblox_python_support(version_info):
|
|
188
|
+
"""Return (supported, detail) for the embedded PictoBlox interpreter."""
|
|
189
|
+
major, minor, micro = int(version_info[0]), int(version_info[1]), int(version_info[2])
|
|
190
|
+
normal_ok = (major, minor) >= (3, 10) # 3.10 and every newer Python
|
|
191
|
+
pictoblox_compat_ok = (major, minor) == (3, 8)
|
|
192
|
+
ok = normal_ok or pictoblox_compat_ok
|
|
193
|
+
if pictoblox_compat_ok:
|
|
194
|
+
detail = f"{major}.{minor}.{micro} (PictoBlox compatibility host)"
|
|
195
|
+
elif normal_ok:
|
|
196
|
+
detail = f"{major}.{minor}.{micro}"
|
|
197
|
+
else:
|
|
198
|
+
detail = f"{major}.{minor}.{micro} (supported: PictoBlox 3.8.x or normal Python 3.10 or newer)"
|
|
199
|
+
return ok, detail
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _pictoblox_frontend_self_test() -> DoctorItem:
|
|
203
|
+
"""Compile a tiny list-index program through the real Python frontend.
|
|
204
|
+
|
|
205
|
+
This specifically protects the mandatory PictoBlox Python 3.8 host, whose
|
|
206
|
+
AST shape differs from Python 3.9+ for ``a[i]``.
|
|
207
|
+
"""
|
|
208
|
+
from .compiler import Transpiler
|
|
209
|
+
source = """from Barmaja_PyArduino import *
|
|
210
|
+
|
|
211
|
+
leds = [2, 3, 4]
|
|
212
|
+
i = 0
|
|
213
|
+
pinMode(leds[i], OUTPUT)
|
|
214
|
+
|
|
215
|
+
while True:
|
|
216
|
+
digitalWrite(leds[i], HIGH)
|
|
217
|
+
i = (i + 1) % 3
|
|
218
|
+
"""
|
|
219
|
+
try:
|
|
220
|
+
Transpiler().compile_text(source)
|
|
221
|
+
except Exception as exc:
|
|
222
|
+
return DoctorItem(
|
|
223
|
+
"pictoblox_frontend",
|
|
224
|
+
"PictoBlox Python frontend",
|
|
225
|
+
"ERROR",
|
|
226
|
+
f"list-index self-test failed: {exc}",
|
|
227
|
+
)
|
|
228
|
+
return DoctorItem("pictoblox_frontend", "PictoBlox Python frontend", "OK", "list indexing PASS")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _pictoblox_items() -> tuple[DoctorItem, ...]:
|
|
232
|
+
from . import __version__ as current
|
|
233
|
+
from .environment import installed_package_version
|
|
234
|
+
items: list[DoctorItem] = []
|
|
235
|
+
inside = _running_in_pictoblox()
|
|
236
|
+
if inside:
|
|
237
|
+
py = sys.version_info
|
|
238
|
+
# PictoBlox V1 embeds CPython 3.8.10 on current Windows releases.
|
|
239
|
+
# Normal Python is a 3.10-and-newer product target, but the mandatory
|
|
240
|
+
# PictoBlox host is a deliberate compatibility exception.
|
|
241
|
+
ok, detail = _pictoblox_python_support(py)
|
|
242
|
+
items.append(DoctorItem("pictoblox_python", "PictoBlox Python", "OK" if ok else "ERROR", detail))
|
|
243
|
+
here = Path(__file__).resolve().parent
|
|
244
|
+
items.append(DoctorItem("pictoblox_loaded", "Barmaja_PyArduino loaded from", "OK", f"{here} (version {current})"))
|
|
245
|
+
items.append(_pictoblox_frontend_self_test())
|
|
246
|
+
copies = []
|
|
247
|
+
for entry in sys.path:
|
|
248
|
+
try:
|
|
249
|
+
candidate = Path(entry or ".").resolve() / "Barmaja_PyArduino"
|
|
250
|
+
if (candidate / "__init__.py").exists() and candidate.resolve() != here:
|
|
251
|
+
copies.append(f"{candidate} ({installed_package_version(candidate) or 'unknown'})")
|
|
252
|
+
except OSError:
|
|
253
|
+
continue
|
|
254
|
+
if copies:
|
|
255
|
+
items.append(DoctorItem("pictoblox_duplicates", "Other Barmaja_PyArduino copies", "WARN", "; ".join(copies)))
|
|
256
|
+
dirs = pictoblox_module_dirs()
|
|
257
|
+
if not dirs:
|
|
258
|
+
items.append(DoctorItem("pictoblox", "PictoBlox", "ERROR" if not inside else "WARN",
|
|
259
|
+
"python_modules directory not detected (Documents\\PictoBlox\\python_modules)"))
|
|
260
|
+
return tuple(items)
|
|
261
|
+
if len(dirs) > 1:
|
|
262
|
+
items.append(DoctorItem(
|
|
263
|
+
"pictoblox_multiple_dirs", "Multiple PictoBlox module directories", "WARN",
|
|
264
|
+
"; ".join(str(p) for p in dirs),
|
|
265
|
+
))
|
|
266
|
+
for p in dirs:
|
|
267
|
+
pkg = p / "Barmaja_PyArduino"
|
|
268
|
+
exists = p.is_dir()
|
|
269
|
+
writable = os.access(p, os.W_OK) if exists else os.access(p.parent, os.W_OK)
|
|
270
|
+
items.append(DoctorItem("pictoblox", "PictoBlox modules", "OK" if exists else "WARN",
|
|
271
|
+
str(p) if exists else f"{p} (will be created by offline installer)"))
|
|
272
|
+
items.append(DoctorItem("pictoblox_write", "PictoBlox modules writable", "OK" if writable else "ERROR", "yes" if writable else "no"))
|
|
273
|
+
if not pkg.exists():
|
|
274
|
+
items.append(DoctorItem("pictoblox_import", "Barmaja_PyArduino in PictoBlox", "ERROR", "not installed; run the Barmaja offline installer"))
|
|
275
|
+
continue
|
|
276
|
+
installed = installed_package_version(pkg)
|
|
277
|
+
if installed != current:
|
|
278
|
+
items.append(DoctorItem("pictoblox_import", "Barmaja_PyArduino in PictoBlox", "ERROR",
|
|
279
|
+
f"version {installed or 'unknown'} installed, {current} expected; run the Barmaja offline installer again"))
|
|
280
|
+
else:
|
|
281
|
+
items.append(DoctorItem("pictoblox_import", "Barmaja_PyArduino in PictoBlox", "OK", f"installed ({installed})"))
|
|
282
|
+
return tuple(items)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _bundled_discovery_tools_item(cli: str) -> DoctorItem | None:
|
|
286
|
+
"""Check the four Arduino CLI builtin helpers shipped by the Full Pack."""
|
|
287
|
+
try:
|
|
288
|
+
cli_path = Path(cli).resolve()
|
|
289
|
+
runtime = (ensure_runtime_dirs()["root"] / "runtime").resolve()
|
|
290
|
+
if cli_path.parent != runtime:
|
|
291
|
+
return None
|
|
292
|
+
except OSError:
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
required = {
|
|
296
|
+
"serial-discovery": "serial-discovery.exe" if os.name == "nt" else "serial-discovery",
|
|
297
|
+
"mdns-discovery": "mdns-discovery.exe" if os.name == "nt" else "mdns-discovery",
|
|
298
|
+
"dfu-discovery": "dfu-discovery.exe" if os.name == "nt" else "dfu-discovery",
|
|
299
|
+
"serial-monitor": "serial-monitor.exe" if os.name == "nt" else "serial-monitor",
|
|
300
|
+
}
|
|
301
|
+
tools_root = runtime / "arduino-data" / "packages" / "builtin" / "tools"
|
|
302
|
+
missing = []
|
|
303
|
+
for family, exe_name in required.items():
|
|
304
|
+
family_root = tools_root / family
|
|
305
|
+
found = any((version_dir / exe_name).is_file() for version_dir in family_root.iterdir()) if family_root.is_dir() else False
|
|
306
|
+
if not found:
|
|
307
|
+
missing.append(family)
|
|
308
|
+
if missing:
|
|
309
|
+
return DoctorItem(
|
|
310
|
+
"discovery_tools",
|
|
311
|
+
"Arduino USB discovery tools",
|
|
312
|
+
"ERROR",
|
|
313
|
+
"missing: " + ", ".join(missing) + "; reinstall the Full Offline package",
|
|
314
|
+
)
|
|
315
|
+
return DoctorItem("discovery_tools", "Arduino USB discovery tools", "OK", "present")
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def run_doctor(*, details: bool = False, pictoblox: bool = False, use_cache: bool = True) -> DoctorReport:
|
|
319
|
+
if pictoblox:
|
|
320
|
+
return DoctorReport(_pictoblox_items(), source="PictoBlox")
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
from .toolchain import find_arduino_cli
|
|
324
|
+
cli = find_arduino_cli()
|
|
325
|
+
except Exception:
|
|
326
|
+
cli = None
|
|
327
|
+
items: list[DoctorItem] = []
|
|
328
|
+
if not cli:
|
|
329
|
+
items.append(DoctorItem("arduino_cli", "Arduino environment", "ERROR", "Arduino CLI/toolchain not found"))
|
|
330
|
+
return DoctorReport(tuple(items), source=None)
|
|
331
|
+
|
|
332
|
+
_, cli_item = _cli_version(cli)
|
|
333
|
+
items.append(cli_item)
|
|
334
|
+
discovery_item = _bundled_discovery_tools_item(cli)
|
|
335
|
+
if discovery_item is not None:
|
|
336
|
+
items.append(discovery_item)
|
|
337
|
+
_, core_item = _board_core(cli)
|
|
338
|
+
items.append(core_item)
|
|
339
|
+
lib_markers, lib_items = _libraries(cli)
|
|
340
|
+
items.extend(lib_items)
|
|
341
|
+
|
|
342
|
+
quick_fp = make_fingerprint(cli, libraries=_pinned_library_markers())
|
|
343
|
+
full_fp = make_fingerprint(cli, libraries=lib_markers)
|
|
344
|
+
cached = load_cache() if use_cache else None
|
|
345
|
+
full_cache_match = bool(_cache_is_content_safe(full_fp) and cached and cached.get("fingerprint") == full_fp.digest() and cached.get("status") == "READY")
|
|
346
|
+
|
|
347
|
+
# A first/full/manual diagnosis proves that the Uno core/compiler can really compile.
|
|
348
|
+
# A matching deep cache may skip this expensive step; any real compile/upload failure
|
|
349
|
+
# invalidates the cache in toolchain.py.
|
|
350
|
+
if not full_cache_match or details:
|
|
351
|
+
if core_item.status != "ERROR":
|
|
352
|
+
items.append(_compile_capability(cli, verbose=details))
|
|
353
|
+
|
|
354
|
+
from .toolchain import bundled_arduino_cli
|
|
355
|
+
bundled = bundled_arduino_cli()
|
|
356
|
+
source = "Barmaja bundled runtime" if bundled and Path(bundled).resolve() == Path(cli).resolve() else f"Arduino environment ({cli})"
|
|
357
|
+
report = DoctorReport(tuple(items), source=source)
|
|
358
|
+
if report.ok:
|
|
359
|
+
save_cache(full_fp, quick_fingerprint=quick_fp, source=report.source)
|
|
360
|
+
else:
|
|
361
|
+
invalidate_cache()
|
|
362
|
+
return report
|
|
363
|
+
|
|
364
|
+
def doctor_pictoblox(*, details: bool = False) -> str:
|
|
365
|
+
"""In-Python doctor for PictoBlox: package install + the Arduino toolchain used by upload()."""
|
|
366
|
+
package = run_doctor(details=details, pictoblox=True, use_cache=False)
|
|
367
|
+
environment = run_doctor(details=details, pictoblox=False, use_cache=not details)
|
|
368
|
+
report = DoctorReport(package.items + environment.items, source=environment.source)
|
|
369
|
+
return report.detailed() if details else report.concise()
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _pinned_library_markers() -> list[Path]:
|
|
373
|
+
private = ensure_runtime_dirs()["root"] / "arduino-libraries"
|
|
374
|
+
return sorted(private.glob("*/library.properties")) if private.exists() else []
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _cache_is_content_safe(fp) -> bool:
|
|
378
|
+
# Only the Full Pack has a known immutable layout that we can fingerprint
|
|
379
|
+
# strongly enough to skip a real compile. Existing IDE/PictoBlox environments
|
|
380
|
+
# are still supported, but Doctor performs the compile instead of trusting cache.
|
|
381
|
+
return bool(getattr(fp, "integrity_markers", ()))
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def quick_environment_ok() -> bool:
|
|
385
|
+
"""Safe fast path for an unchanged pinned Full-Pack environment."""
|
|
386
|
+
try:
|
|
387
|
+
from .toolchain import find_arduino_cli
|
|
388
|
+
cli = find_arduino_cli()
|
|
389
|
+
except Exception:
|
|
390
|
+
invalidate_cache(); return False
|
|
391
|
+
fp = make_fingerprint(cli, libraries=_pinned_library_markers())
|
|
392
|
+
if not _cache_is_content_safe(fp):
|
|
393
|
+
return False
|
|
394
|
+
cached = load_cache()
|
|
395
|
+
return bool(cached and cached.get("status") == "READY" and cached.get("quick_fingerprint", cached.get("fingerprint")) == fp.digest())
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def ensure_environment_ready() -> None:
|
|
399
|
+
"""Silent normal-run check. Cache accelerates success but never masks failures."""
|
|
400
|
+
if quick_environment_ok():
|
|
401
|
+
return
|
|
402
|
+
report = run_doctor(details=False, pictoblox=False, use_cache=True)
|
|
403
|
+
if not report.ok:
|
|
404
|
+
from .errors import ToolchainError
|
|
405
|
+
problems = "; ".join(f"{i.label}: {i.detail}" for i in report.problems)
|
|
406
|
+
raise ToolchainError(problems or "Arduino environment is not ready.")
|