Files
MicrOBU/microbu-esp32c5/tools/v2x2map-0.3.0/bridge/build_deb.py
T
Ashin Walpola 0e9525162d Keep the colleague's microbu-esp32c5 tree in this repository
obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but
that tree was gitignored, so a clone of this repository could not build the
firmware it ships. It is now committed here as ordinary files in its own
folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP
bridge's signature verification (--trust) used on the bench. Nothing is
fetched from or pushed to the colleague's repository; this repository and
its remotes carry everything. The folder's own .gitignore keeps build output,
downloaded components and private key material out, as it did there; the
committed file set is identical to that repository's tracked files.

The ESP32-C5 is still flashed from obu-firmware/, which only takes
vanetza-idf from microbu-esp32c5/, so the two stay separate folders.
FLASHING.md says how to take a newer version of the colleague's tree (copy
it over the folder, rebuild, test, commit).
2026-09-23 17:46:40 +02:00

233 lines
7.6 KiB
Python

"""
Builds v2x2map-bridge_<version>_all.deb for Debian/Ubuntu.
A .deb is an ar(1) archive containing three members:
debian-binary plain text "2.0\\n"
control.tar.gz DEBIAN/ metadata
data.tar.gz the files to install
No external tools required — pure Python (tarfile + gzip).
Run from V2X2MAP/bridge:
python build_deb.py
Output: dist/v2x2map-bridge_0.2.1_all.deb
"""
import gzip
import hashlib
import io
import os
import stat
import tarfile
import time
VERSION = "0.2.1"
PACKAGE = "v2x2map-bridge"
ARCH = "all"
# ── ar archive ───────────────────────────────────────────────────────────────
def _ar_header(name: str, size: int) -> bytes:
ts = str(int(time.time()))
return (
name.encode().ljust(16)[:16]
+ ts.encode().ljust(12)[:12]
+ b"0 " # uid
+ b"0 " # gid
+ b"100644 " # mode
+ str(size).encode().ljust(10)[:10]
+ b"`\n"
)
def ar_pack(members: list[tuple[str, bytes]]) -> bytes:
buf = bytearray(b"!<arch>\n")
for name, data in members:
buf += _ar_header(name, len(data))
buf += data
if len(data) % 2:
buf += b"\n" # ar entries must start on even offsets
return bytes(buf)
# ── tar.gz builder ────────────────────────────────────────────────────────────
def make_targz(entries: list) -> bytes:
"""
entries is a list of:
("./path/to/dir/", None, 0o755) → directory
("./path/to/file", bytes_content, 0o644) → regular file
"""
raw = io.BytesIO()
with gzip.GzipFile(fileobj=raw, mode="wb", mtime=0) as gz:
tf = tarfile.open(fileobj=gz, mode="w:")
for path, content, mode in entries:
info = tarfile.TarInfo(name=path)
info.mtime = 0
info.uid = 0
info.gid = 0
info.uname = "root"
info.gname = "root"
if content is None:
info.type = tarfile.DIRTYPE
info.mode = mode
tf.addfile(info)
else:
info.mode = mode
info.size = len(content)
tf.addfile(info, io.BytesIO(content))
tf.close()
return raw.getvalue()
# ── file content helpers ───────────────────────────────────────────────────────
def _read(rel: str) -> bytes:
with open(rel, "rb") as f:
return f.read()
def _txt(s: str) -> bytes:
return s.encode("utf-8")
# ── package metadata ──────────────────────────────────────────────────────────
WRAPPER = _txt("""\
#!/bin/bash
exec "${V2X2MAP_PYTHON:-python3}" /usr/lib/v2x2map/its_g5_bridge.py "$@"
""")
CONTROL = _txt(f"""\
Package: {PACKAGE}
Version: {VERSION}
Architecture: {ARCH}
Maintainer: pit711
Installed-Size: 250
Depends: python3 (>= 3.10), python3-serial, python3-paho-mqtt
Description: V2X2MAP ITS-G5 USB-to-MQTT bridge and live dashboard
Captures IEEE 802.11p / ITS-G5 frames from an ESP32-C5 via USB serial
and republishes them to one or more MQTT brokers.
.
Includes a live web dashboard (http://127.0.0.1:8080) with:
- Map view with type-specific vehicle/RSU markers
- Grouped frame log with filter chips
- Statistics tab (type breakdown, sparkline, broker status)
- MAC forensics tab (first/last seen, frame count, CSV export)
- Runtime PCAP recording
.
Config : ~/.config/v2x2map/v2x2map.cfg (JSON, created on first run)
Records : ~/v2x2map/recordings/its5_*.pcap
.
Quick start:
v2x2map-bridge --node-id V2X2MAP:d2cf13ed6293
.
Subsequent runs (reads config automatically):
v2x2map-bridge
""")
POSTINST = _txt("""\
#!/bin/bash
set -e
echo ""
echo " V2X2MAP ITS-G5 bridge installed."
echo " For VAM decoding install requirements.txt in a venv and set V2X2MAP_PYTHON to its python."
echo " Usage: v2x2map-bridge --node-id V2X2MAP:<12-hex-mac>"
echo " Dashboard opens at http://127.0.0.1:8080"
echo ""
echo " Serial port access: add your user to the 'dialout' group:"
echo " sudo usermod -aG dialout $USER (re-login required)"
echo ""
""")
PRERM = _txt("""\
#!/bin/bash
set -e
""")
COPYRIGHT = _txt(f"""\
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: {PACKAGE}
Source: https://github.com/pit711/v2x2map
Files: *
Copyright: 2026 pit711
License: MIT
""")
# ── md5sums ───────────────────────────────────────────────────────────────────
def md5sums(file_entries: list[tuple[str, bytes]]) -> bytes:
lines = []
for path, content, _ in file_entries:
if content is not None:
h = hashlib.md5(content).hexdigest()
lines.append(f"{h} {path.lstrip('./')}")
return _txt("\n".join(lines) + "\n")
# ── build ─────────────────────────────────────────────────────────────────────
def build():
os.makedirs("dist", exist_ok=True)
bridge_py = _read("its_g5_bridge.py")
server_py = _read("dashboard_server.py")
dash_html = _read("dashboard.html")
# Files that will be installed (used for md5sums too)
data_files = [
("./usr/lib/v2x2map/its_g5_bridge.py", bridge_py, 0o644),
("./usr/lib/v2x2map/dashboard_server.py", server_py, 0o644),
("./usr/lib/v2x2map/dashboard.html", dash_html, 0o644),
("./usr/bin/v2x2map-bridge", WRAPPER, 0o755),
("./usr/share/doc/v2x2map-bridge/copyright", COPYRIGHT, 0o644),
]
for name in ("vam.py", "requirements.txt", "asn1/VAM-PDU-Descriptions.asn",
"asn1/TS102894-2v241-CDD.asn", "asn1/COPYRIGHT.md"):
data_files.append(("./usr/lib/v2x2map/" + name, _read(name), 0o644))
data_entries = [
("./", None, 0o755),
("./usr/", None, 0o755),
("./usr/lib/", None, 0o755),
("./usr/lib/v2x2map/", None, 0o755),
("./usr/bin/", None, 0o755),
("./usr/share/", None, 0o755),
("./usr/share/doc/", None, 0o755),
("./usr/share/doc/v2x2map-bridge/", None, 0o755),
] + data_files
ctrl_entries = [
("./", None, 0o755),
("./control", CONTROL, 0o644),
("./md5sums", md5sums(data_files), 0o644),
("./postinst", POSTINST, 0o755),
("./prerm", PRERM, 0o755),
]
control_tgz = make_targz(ctrl_entries)
data_tgz = make_targz(data_entries)
deb = ar_pack([
("debian-binary", b"2.0\n"),
("control.tar.gz", control_tgz),
("data.tar.gz", data_tgz),
])
name = f"{PACKAGE}_{VERSION}_{ARCH}.deb"
out = os.path.join("dist", name)
with open(out, "wb") as f:
f.write(deb)
kb = len(deb) // 1024
print(f"Built: {out} ({kb} KB)")
print(f"Install with:")
print(f" sudo apt install ./{name}")
print(f" -- or --")
print(f" sudo dpkg -i {name}")
if __name__ == "__main__":
build()