-
Notifications
You must be signed in to change notification settings - Fork 0
Check and Download MCU Firmware
This page documents the vendor MCU firmware download path and local validation steps for wrapped Photonicat 2 MCU firmware files.
Check the running MCU version first with MCU Version Check.
The vendor OpenWrt MCU production check reads this manifest:
curl -fL -o /tmp/pcat2-mcu-latest.json \
https://dl.photonicat.com/firmware/pcat2_mcu/latest_img.jsonRead the firmware URL from the manifest. Do not derive a download URL from the device version string.
python3 - /tmp/pcat2-mcu-latest.json <<'PY'
import json
import pathlib
import sys
import urllib.parse
base = "https://dl.photonicat.com/firmware/pcat2_mcu/"
data = json.loads(pathlib.Path(sys.argv[1]).read_text())
firmware = data.get("url", "")
if firmware and not firmware.startswith(("http://", "https://")):
firmware = urllib.parse.urljoin(base, firmware)
print(f"version={data.get('version', '')}")
print(f"url={firmware}")
print(f"sha256={data.get('sha256sum') or data.get('sha256') or ''}")
print(f"date={data.get('date') or data.get('build_date') or ''}")
PYThe full OpenWrt image's pcat-manager-web bytecode uses the manifest's url field and prefixes relative filenames with:
https://dl.photonicat.com/firmware/pcat2_mcu/
For normal updates, use the manifest endpoint rather than relying on a directory listing. For older releases, use the multi-version index below.
The vendor serves a second index alongside latest_img.json that lists older wrapped packages:
curl -fL -o /tmp/pcat2-mcu-versions.json \
https://dl.photonicat.com/firmware/pcat2_mcu/versions.jsonIt returns a versions array whose entries use the same fields as the production manifest:
{
"versions": [
{
"version": "RA2E1260702000",
"url": "ota_RA2E120260702104522.bin",
"date": "260702",
"changelog-md": "",
"sha256sum": "ed10b1ae7015c1fe54184e03b9222824cf37525ae642b2dddaee197c168dffa0",
"message": "Fix broken RTC reading.",
"upload_timestamp": "2026-07-02 10:45:22"
}
]
}List what it currently offers:
python3 - /tmp/pcat2-mcu-versions.json <<'PY'
import json
import pathlib
import sys
data = json.loads(pathlib.Path(sys.argv[1]).read_text())
for entry in data.get("versions", []):
print(entry.get("version", ""), entry.get("url", ""), entry.get("sha256sum", ""))
PYThis index is not complete. Observed on 2026-08-14 it listed RA2E1260702000, RA2E1260515000, and RA2E1260306000, and did not list the then-current manifest target RA2E1260813002 or the earlier RA2E1260730001. Unlisted filenames still download from the same directory when the exact name is known, so treat the index as a convenience listing, not as the set of available files.
No shipped pcat-manager-web build reads this endpoint. The pcat-manager-web_2.2.1-146 IPK still references only latest_img.json, so the version list is server-side and has no vendor UI.
Download an older package by name from the index:
curl -fL -o /tmp/pcat2-mcu-older.bin \
https://dl.photonicat.com/firmware/pcat2_mcu/ota_RA2E120260702104522.binValidate it with the wrapper checks below and confirm the version is the intended rollback target before flashing. Checked on 2026-08-14, ota_RA2E120260702104522.bin downloads with the listed SHA256 and passes the wrapper checks (ARBDPHC2, version RA2E1260702000, wrapped 74564 bytes, payload 74052 bytes, CRC32 valid). Flashing a version older than the running one is a downgrade and requires the updater's force option.
https://dl.photonicat.com/firmware/pcat2_mcu/ota.bin
This URL tracks the current production manifest target. Checked on 2026-08-14 it was byte-identical to the latest_img.json package RA2E1260813002 (SHA256 46ba856cdbc493f01d560581ea5cb6a4499ecb153be10576f7de00f106136d98), and on 2026-08-08 it served the then-current RA2E1260730001. Do not treat it as a previous-version image. Use versions.json for older releases.
After reviewing the manifest, download the referenced file without hardcoding the filename:
FIRMWARE_URL=$(python3 - /tmp/pcat2-mcu-latest.json <<'PY'
import json
import pathlib
import sys
import urllib.parse
base = "https://dl.photonicat.com/firmware/pcat2_mcu/"
data = json.loads(pathlib.Path(sys.argv[1]).read_text())
firmware = data["url"]
if not firmware.startswith(("http://", "https://")):
firmware = urllib.parse.urljoin(base, firmware)
print(firmware)
PY
)
curl -fL -o /tmp/pcat2-firmware.bin \
"$FIRMWARE_URL"Verify the downloaded file against the manifest checksum when one is present:
EXPECTED_SHA256=$(python3 - /tmp/pcat2-mcu-latest.json <<'PY'
import json
import pathlib
import sys
data = json.loads(pathlib.Path(sys.argv[1]).read_text())
print(data.get("sha256sum") or data.get("sha256") or "")
PY
)
ACTUAL_SHA256=$(sha256sum /tmp/pcat2-firmware.bin | awk '{print $1}')
test -n "$EXPECTED_SHA256"
test "$EXPECTED_SHA256" = "$ACTUAL_SHA256"Also keep the checksum in your notes:
sha256sum /tmp/pcat2-firmware.binImage extraction is a research or recovery path, not the normal production download path.
Search extracted images for wrapped firmware files by header magic:
rg -a -l 'ARBDPHC2' /tmp/extracted-imageSearch extracted filesystems for PMU version strings:
rg -a -o 'RA2E1[0-9]{9}' /tmp/extracted-rootIdentify candidate wrapped firmware files from an extracted filesystem:
find /tmp/extracted-root -type f -size +512c -exec sh -c '
for f do
if head -c 8 "$f" | grep -q "ARBDPHC2"; then
printf "%s\n" "$f"
fi
done
' sh {} +A local version string is not the same thing as a downloadable firmware package. Only a file with the ARBDPHC2 wrapper is valid input for pcat-pmu-updater.
pcat-pmu-updater accepts a wrapped firmware file:
| Offset | Size | Meaning |
|---|---|---|
0 |
8 |
ASCII magic ARBDPHC2
|
8 |
14 |
ASCII firmware version, must start with RA2E1
|
22 |
4 |
Little-endian raw firmware size |
26 |
4 |
Little-endian CRC32 of raw firmware payload |
30 |
8 |
Little-endian package timestamp |
38 |
474 |
Padding to 512-byte header |
512 |
variable | Raw RA2E1 firmware payload |
The upstream packer is:
rockchip_rk3568_pcat_manager/src/pcat-fwgen.py
The updater validates the wrapper, strips the first 512 bytes, and sends only the raw firmware payload to the PMU.
Use this local validator before flashing:
python3 - /tmp/pcat2-firmware.bin <<'PY'
import pathlib
import struct
import sys
import zlib
path = pathlib.Path(sys.argv[1])
data = path.read_bytes()
if len(data) < 512:
raise SystemExit("too small")
if data[:8] != b"ARBDPHC2":
raise SystemExit("bad magic")
version = data[8:22].decode("ascii", "replace")
if not version.startswith("RA2E1"):
raise SystemExit(f"bad version: {version}")
raw_size, expected_crc = struct.unpack_from("<II", data, 22)
if raw_size + 512 != len(data):
raise SystemExit(f"bad size: raw={raw_size} file={len(data)}")
raw = data[512:]
actual_crc = zlib.crc32(raw) & 0xffffffff
if actual_crc != expected_crc:
raise SystemExit(f"bad crc: expected={expected_crc:08x} actual={actual_crc:08x}")
print(f"path={path}")
print(f"version={version}")
print(f"raw_size={raw_size}")
print(f"crc32={actual_crc:08x}")
PYOnly flash if validation succeeds and the version is the intended target.