Skip to content

Commit a45cd5e

Browse files
committed
Setup proper pip package for pandacan (#2239)
- Add setup.py with custom build command to optionally build firmware - Add MANIFEST.in to include firmware, certs, and headers in package - Update pyproject.toml with proper package metadata and CLI entry points - Make opendbc import optional to prevent installation failures - Add CLI entry points for panda-health, panda-flash, and panda-version - Convert script main functions to be callable from CLI entry points This allows pandacan to be installed as a standard pip package with all necessary components including firmware, certificates, and build tools.
1 parent e42367d commit a45cd5e

File tree

7 files changed

+105
-8
lines changed

7 files changed

+105
-8
lines changed

MANIFEST.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
recursive-include board *.h
2+
include board/obj/*.bin
3+
include board/obj/*.signed
4+
recursive-include certs *
5+
include README.md
6+
include LICENSE
7+

pyproject.toml

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,43 @@
11
[project]
22
name = "pandacan"
3-
version = "0.0.10"
3+
version = "0.0.11"
44
description = "Code powering the comma.ai panda"
55
readme = "README.md"
66
requires-python = ">=3.11,<3.13" # macOS doesn't work with 3.13 due to pycapnp from opendbc
77
license = {text = "MIT"}
88
authors = [{name = "comma.ai"}]
9+
maintainers = [{name = "comma.ai"}]
10+
keywords = ["panda", "can", "vehicle", "comma", "openpilot", "hardware", "embedded"]
911
classifiers = [
12+
"Development Status :: 4 - Beta",
13+
"Intended Audience :: Developers",
14+
"License :: OSI Approved :: MIT License",
1015
"Natural Language :: English",
16+
"Operating System :: POSIX :: Linux",
17+
"Operating System :: MacOS",
18+
"Operating System :: Microsoft :: Windows",
1119
"Programming Language :: Python :: 3",
20+
"Programming Language :: Python :: 3.11",
21+
"Programming Language :: Python :: 3.12",
1222
"Topic :: System :: Hardware",
23+
"Topic :: System :: Hardware :: Hardware Drivers",
24+
"Topic :: Software Development :: Embedded Systems",
1325
]
1426
dependencies = [
1527
"libusb1",
16-
"opendbc @ git+https://github.com/commaai/opendbc.git@master#egg=opendbc",
1728
]
1829

30+
[project.urls]
31+
Homepage = "https://github.com/commaai/panda"
32+
Repository = "https://github.com/commaai/panda"
33+
"Bug Reports" = "https://github.com/commaai/panda/issues"
34+
Changelog = "https://github.com/commaai/panda/blob/master/README.md"
35+
36+
[project.scripts]
37+
panda-health = "panda.scripts.can_health:main"
38+
panda-flash = "panda.scripts.reflash_internal_panda:main"
39+
panda-version = "panda.scripts.get_version:main"
40+
1941
[project.optional-dependencies]
2042
dev = [
2143
"scons",
@@ -30,6 +52,7 @@ dev = [
3052
"mypy",
3153
"setuptools",
3254
"spidev; platform_system == 'Linux'",
55+
"opendbc @ git+https://github.com/commaai/opendbc.git@master#egg=opendbc",
3356
]
3457

3558
[build-system]
@@ -38,10 +61,19 @@ build-backend = "setuptools.build_meta"
3861

3962
[tool.setuptools]
4063
packages = ["panda"]
64+
include-package-data = true
4165

4266
[tool.setuptools.package-dir]
4367
panda = "."
4468

69+
[tool.setuptools.package-data]
70+
"panda" = [
71+
"board/**/*.h",
72+
"board/obj/*.bin",
73+
"board/obj/*.signed",
74+
"certs/**",
75+
]
76+
4577
[tool.mypy]
4678
# third-party packages
4779
ignore_missing_imports = true

python/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
from functools import wraps, partial
1010
from itertools import accumulate
1111

12-
from opendbc.car.structs import CarParams
12+
try:
13+
from opendbc.car.structs import CarParams
14+
except ImportError:
15+
# opendbc is optional, create a minimal CarParams stub
16+
class CarParams:
17+
class SafetyModel:
18+
silent = 0
1319

1420
from .base import BaseHandle
1521
from .constants import FW_PATH, McuType
@@ -18,7 +24,7 @@
1824
from .usb import PandaUsbHandle
1925
from .utils import logger
2026

21-
__version__ = '0.0.10'
27+
__version__ = '0.0.11'
2228

2329
CANPACKET_HEAD_SIZE = 0x6
2430
DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64]

scripts/can_health.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ def colorize_errors(value):
1414
return f'{RED}{value}\033[0m'
1515
return str(value)
1616

17-
if __name__ == "__main__":
18-
17+
def main():
1918
panda = Panda()
2019
while True:
2120
print(chr(27) + "[2J") # clear screen
@@ -27,3 +26,6 @@ def colorize_errors(value):
2726
print(f"{key}: {colorize_errors(value)} ", end=" ")
2827
print()
2928
time.sleep(1)
29+
30+
if __name__ == "__main__":
31+
main()

scripts/get_version.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#!/usr/bin/env python3
22
from panda import Panda
33

4-
if __name__ == "__main__":
4+
def main():
55
for p in Panda.list():
66
pp = Panda(p)
77
print(f"{pp.get_serial()[0]}: {pp.get_version()}")
8+
9+
if __name__ == "__main__":
10+
main()

scripts/reflash_internal_panda.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def gpio_set(pin, high):
1616
f.write(b"1" if high else b"0")
1717

1818

19-
if __name__ == "__main__":
19+
def main():
2020
for pin in (GPIO.STM_RST_N, GPIO.STM_BOOT0):
2121
gpio_init(pin, True)
2222

@@ -38,3 +38,6 @@ def gpio_set(pin, high):
3838
p = Panda()
3939
assert p.bootstub
4040
p.flash()
41+
42+
if __name__ == "__main__":
43+
main()

setup.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from setuptools import setup
2+
from setuptools.command.build_py import build_py
3+
import subprocess
4+
import os
5+
import shutil
6+
7+
class BuildPyWithFW(build_py):
8+
def run(self):
9+
# Check if ARM toolchain is available
10+
toolchain_available = self._check_toolchain()
11+
12+
if toolchain_available:
13+
print("ARM toolchain found, building firmware...")
14+
try:
15+
result = subprocess.run(['scons'], cwd=os.path.dirname(__file__),
16+
capture_output=True, text=True, timeout=300)
17+
if result.returncode != 0:
18+
print("SCons build failed:")
19+
print("STDOUT:", result.stdout)
20+
print("STDERR:", result.stderr)
21+
print("Warning: Firmware build failed, but continuing with package build.")
22+
print("Note: The package will not include firmware binaries.")
23+
print("To include firmware, install ARM GCC toolchain and rebuild.")
24+
else:
25+
print("Firmware build completed successfully.")
26+
except subprocess.TimeoutExpired:
27+
print("Firmware build timed out after 5 minutes.")
28+
print("Continuing with package build without firmware.")
29+
except FileNotFoundError:
30+
print("SCons not found. Install with: pip install scons")
31+
print("Continuing with package build without firmware.")
32+
else:
33+
print("ARM GCC toolchain not found.")
34+
print("Continuing with package build without firmware binaries.")
35+
print("To include firmware, install arm-none-eabi-gcc and rebuild.")
36+
37+
super().run()
38+
39+
def _check_toolchain(self):
40+
"""Check if ARM GCC toolchain is available."""
41+
return shutil.which('arm-none-eabi-gcc') is not None
42+
43+
setup(cmdclass={'build_py': BuildPyWithFW})
44+

0 commit comments

Comments
 (0)