import React, { useEffect, useState } from "react"; import { View, Text, StyleSheet, Alert, Platform } from "react-native"; import { NativeStackScreenProps } from "@react-navigation/native-stack"; import { RootStackParamList } from "../App"; import RNFS from "react-native-fs"; import { startDfu, getDfuTargetId, DfuProgressEvent, DfuStateEvent, } from "@systemic-games/react-native-nordic-nrf5-dfu"; import { useTranslation } from "react-i18next"; import { encode as btoa } from "base-64"; import MyStatusbar from "./component/MyStatusbar"; import MyHeader from "./component/MyHeader"; type Props = NativeStackScreenProps; interface DeviceInfo { hardware: number; latestFirmware: string; download: string; } interface ParsedFirmware { hardware: number; iteration: number; build: string; raw: string; } const bytesToBase64 = (bytes: Uint8Array): string => { let binary = ""; const chunkSize = 0x8000; for (let i = 0; i < bytes.length; i += chunkSize) { const chunk = bytes.subarray(i, i + chunkSize); binary += String.fromCharCode(...chunk); } return btoa(binary); }; const parseFirmwareVersion = (text: string): ParsedFirmware => { const raw = String(text ?? "").trim(); const parts = raw.split("."); if (parts.length < 2) { throw new Error(`固件版本格式不正确: ${raw}`); } const hardware = parseInt(parts[0], 10); const iteration = parseInt(parts[1], 10); const build = parts.length >= 3 ? parts[2] : ""; if (Number.isNaN(hardware) || Number.isNaN(iteration)) { throw new Error(`固件版本无法解析: ${raw}`); } return { hardware, iteration, build, raw, }; }; export default function DfuScreen({ route, navigation }: Props) { const { t } = useTranslation(); const { deviceId, systemId, address, name, firmware: deviceFirmware, } = route.params; const [progress, setProgress] = useState(0); const [state, setState] = useState("准备中..."); const [error, setError] = useState(); const [latestVersion, setLatestVersion] = useState("读取中..."); const [isDfuRunning, setIsDfuRunning] = useState(false); const mapDfuStateToChinese = (s: string): string => { switch (s) { case "connecting": return "连接中…"; case "starting": return "初始化中…"; case "enablingDfuMode": return "启用 DFU 模式…"; case "uploading": return "上传固件中…"; case "validating": return "校验固件…"; case "disconnecting": return "断开连接…"; case "completed": return "升级完成"; case "aborted": return "已取消"; case "failed": case "dfu_failed": return "升级失败"; case "initializing": return "启动中…"; case "errored": return "升级出错!"; default: return s; } }; useEffect(() => { const unsubscribe = navigation.addListener("beforeRemove", (e) => { if (!isDfuRunning) return; e.preventDefault(); Alert.alert("请稍候", "正在升级,请勿返回或关闭应用!"); }); return unsubscribe; }, [navigation, isDfuRunning]); useEffect(() => { const runDfu = async () => { try { setIsDfuRunning(true); setError(undefined); const rawDeviceId = String(deviceId ?? "").trim(); const rawSystemId = String(systemId ?? rawDeviceId).trim(); const rawAddress = typeof address === "number" ? address : address !== undefined && address !== null ? Number(address) : undefined; const safeDeviceId = getDfuTargetId({ systemId: rawSystemId, address: rawAddress, }); const firmwareText = String(deviceFirmware ?? "").trim(); console.log("🔥 rawDeviceId =", rawDeviceId); console.log("🔥 rawSystemId =", rawSystemId); console.log("🔥 rawAddress =", rawAddress); console.log("🔥 safeDeviceId =", safeDeviceId); console.log("🔥 firmwareText =", JSON.stringify(firmwareText)); if (!safeDeviceId) { throw new Error("无法生成 DFU 目标设备 ID"); } const currentFw = parseFirmwareVersion(firmwareText); console.log("🔥 currentFw =", currentFw); const manifestUrl = "https://powerfun.oss-cn-shanghai.aliyuncs.com/yecongdfu/latest.json"; const manifestPath = RNFS.CachesDirectoryPath + "/latest.json"; console.log("🔥 before fetch manifest"); const manifestResp = await fetch(manifestUrl); console.log("🔥 manifest status =", manifestResp.status); if (!manifestResp.ok) { throw new Error(`manifest 下载失败,HTTP ${manifestResp.status}`); } const manifestText = await manifestResp.text(); console.log("🔥 manifest text =", manifestText); await RNFS.writeFile(manifestPath, manifestText, "utf8"); console.log("🔥 manifest saved =", manifestPath); let manifest: { devices: DeviceInfo[] }; try { manifest = JSON.parse(manifestText) as { devices: DeviceInfo[] }; } catch (e) { throw new Error("manifest 不是合法 JSON: " + manifestText.slice(0, 200)); } const deviceInfo = manifest.devices.find( (d) => d.hardware === currentFw.hardware ); console.log("🔥 matched deviceInfo =", deviceInfo); if (!deviceInfo) { setIsDfuRunning(false); Alert.alert( "无法升级", `未找到 hardware=${currentFw.hardware} 的固件`, [{ text: "确认", onPress: () => navigation.goBack() }] ); return; } setLatestVersion(deviceInfo.latestFirmware); const latestFw = parseFirmwareVersion(deviceInfo.latestFirmware); console.log("🔥 latestFw =", latestFw); if (latestFw.hardware !== currentFw.hardware) { throw new Error( `服务器固件硬件号不匹配:当前 ${currentFw.hardware},服务器 ${latestFw.hardware}` ); } if (latestFw.iteration <= currentFw.iteration) { setIsDfuRunning(false); Alert.alert("无需升级", "已是最新固件,无需升级", [ { text: "确认", onPress: () => navigation.goBack() }, ]); return; } console.log("🔥 upgrade allowed", { currentIteration: currentFw.iteration, latestIteration: latestFw.iteration, }); const zipResp = await fetch(deviceInfo.download); console.log("🔥 zip status =", zipResp.status); if (!zipResp.ok) { throw new Error(`固件包下载失败,HTTP ${zipResp.status}`); } const zipArrayBuffer = await zipResp.arrayBuffer(); const zipBytes = new Uint8Array(zipArrayBuffer); console.log("🔥 zip bytes =", zipBytes.length); const zipBase64 = bytesToBase64(zipBytes); const localPath = RNFS.CachesDirectoryPath + "/firmware.zip"; await RNFS.writeFile(localPath, zipBase64, "base64"); console.log("🔥 zip saved =", localPath); const dfuFilePath = Platform.OS === "android" ? "file://" + localPath : localPath; console.log("🔥 before startDfu =", { safeDeviceId, dfuFilePath, currentFirmware: currentFw.raw, latestFirmware: latestFw.raw, }); await startDfu(safeDeviceId, dfuFilePath, { dfuStateListener: (ev: DfuStateEvent) => { console.log("🔥 dfu state =", ev.state); setState(ev.state); }, dfuProgressListener: (ev: DfuProgressEvent) => { console.log("🔥 dfu progress =", ev.percent); setProgress(ev.percent); }, }); setIsDfuRunning(false); Alert.alert("升级成功", "升级成功,请重连设备", [ { text: "确认", onPress: () => { navigation.reset({ index: 0, routes: [{ name: "Home" }], }); }, }, ]); } catch (err: any) { console.log("❌ runDfu error =", err); setIsDfuRunning(false); setError(err?.message || "DFU失败"); Alert.alert("升级失败", err?.message || "DFU失败", [ { text: "确认", onPress: () => navigation.goBack() }, ]); } }; runDfu(); }, [deviceId, systemId, address, deviceFirmware, navigation]); return ( 蓝牙名称: {name || "--"} 最新版本: {latestVersion} 当前版本: {deviceFirmware || "--"} 升级状态: {mapDfuStateToChinese(state)} {progress}% {!!error && {error}} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#FFFFFF", }, content: { flex: 1, padding: 20, backgroundColor: "#FFFFFF", }, row: { borderBottomWidth: 1, borderBottomColor: "#E7141E", paddingBottom: 6, marginBottom: 12, }, titleText: { fontSize: 18, color: "#111111", fontWeight: "600", }, normalText: { fontSize: 16, color: "#222222", }, progressContainer: { height: 30, backgroundColor: "#EEEEEE", borderRadius: 15, overflow: "hidden", marginTop: 40, justifyContent: "center", }, progressBar: { height: "100%", backgroundColor: "#E7141E", }, progressText: { position: "absolute", alignSelf: "center", fontWeight: "bold", color: "#000000", }, errorText: { color: "red", marginTop: 20, fontSize: 14, }, });