Skip to content

Commit 49e25e5

Browse files
authored
多普勒写频 (#35)
* 支持UVE5欢迎界面写频 * 支持UVE5固件烧录 * UVE5存储区扩大到512KB * 多普勒写频 * UVE5自动校准时间
1 parent 069fe28 commit 49e25e5

File tree

5 files changed

+167
-7
lines changed

5 files changed

+167
-7
lines changed

src/components/navbar/index.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@
175175
import useLocale from '@/hooks/locale';
176176
import useUser from '@/hooks/user';
177177
import Menu from '@/components/menu/index.vue';
178-
import { connect, disconnect, eeprom_init } from '@/utils/serial.js';
178+
import { connect, disconnect, eeprom_init, rtc_sync_beijing } from '@/utils/serial.js';
179179
import { useI18n } from 'vue-i18n';
180180
const { t } = useI18n();
181181
const drivers = import.meta.glob('@/drivers/*.json', { eager: true });
@@ -308,6 +308,12 @@
308308
}
309309
})
310310
311+
// Auto sync device RTC to current Beijing time on connect (best effort).
312+
// Only do this for UVE builds to avoid slowing down other firmwares.
313+
if (/^UVE/i.test(version)) {
314+
try { await rtc_sync_beijing(_connect); } catch {}
315+
}
316+
311317
appStore.updateSettings({ connectState: true, firmwareVersion: version, configuration: _configuration });
312318
}else{
313319
await disconnect(appStore.connectPort);

src/store/modules/app/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ const useAppStore = defineStore('app', {
1616
appDevice(state: AppState) {
1717
return state.device;
1818
},
19+
isUve5(state: AppState): boolean {
20+
return typeof state.firmwareVersion === 'string' && state.firmwareVersion.startsWith('UVE');
21+
},
1922
appAsyncMenus(state: AppState): RouteRecordNormalized[] {
2023
return state.serverMenu as unknown as RouteRecordNormalized[];
2124
},

src/store/modules/app/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,12 @@ export interface AppState {
1616
tabBar: boolean;
1717
menuFromServer: boolean;
1818
serverMenu: RouteRecordNormalized[];
19+
20+
// Device connection/runtime info (set on successful handshake in navbar connect)
21+
connectState?: boolean;
22+
connectPort?: unknown;
23+
firmwareVersion?: string;
24+
configuration?: unknown;
25+
1926
[key: string]: unknown;
2027
}

src/utils/serial.js

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,64 @@ async function eeprom_init(port) {
13681368
return decoder.decode(version.slice(0, version.indexOf(0)));
13691369
}
13701370

1371+
function getBeijingDateParts(date = new Date()) {
1372+
const dtf = new Intl.DateTimeFormat('en-GB', {
1373+
timeZone: 'Asia/Shanghai',
1374+
year: 'numeric',
1375+
month: '2-digit',
1376+
day: '2-digit',
1377+
hour: '2-digit',
1378+
minute: '2-digit',
1379+
second: '2-digit',
1380+
hour12: false,
1381+
});
1382+
const parts = dtf.formatToParts(date);
1383+
const map = {};
1384+
for (const p of parts) {
1385+
if (p.type !== 'literal') map[p.type] = p.value;
1386+
}
1387+
return {
1388+
year: Number(map.year),
1389+
month: Number(map.month),
1390+
day: Number(map.day),
1391+
hour: Number(map.hour),
1392+
minute: Number(map.minute),
1393+
second: Number(map.second),
1394+
};
1395+
}
1396+
1397+
// Sync RTC on connect: send current Beijing time to device.
1398+
// Firmware command:
1399+
// 0x0610 (len=8): uint16 year, uint8 month,day,hour,minute,second, uint8 flags
1400+
// Reply:
1401+
// 0x0611 (len=1): uint8 status (0=OK)
1402+
async function rtc_sync_beijing(port, { timeout = 600 } = {}) {
1403+
sessionStorage.removeItem('webusb')
1404+
const { year, month, day, hour, minute, second } = getBeijingDateParts();
1405+
const year_l = year & 0xff;
1406+
const year_h = (year >> 8) & 0xff;
1407+
const flags = 0x01;
1408+
const packet = new Uint8Array([
1409+
0x10, 0x06, // ID 0x0610
1410+
0x08, 0x00, // len=8
1411+
year_l, year_h,
1412+
month & 0xff,
1413+
day & 0xff,
1414+
hour & 0xff,
1415+
minute & 0xff,
1416+
second & 0xff,
1417+
flags,
1418+
]);
1419+
await sendPacket(port, packet);
1420+
try {
1421+
const resp = await readPacket(port, 0x11, timeout);
1422+
return resp && resp.length >= 5 ? resp[4] : 0xff;
1423+
} catch (e) {
1424+
console.warn('rtc_sync_beijing: no reply (ignored)', e);
1425+
return null;
1426+
}
1427+
}
1428+
13711429
async function eeprom_read(port, address, size = 0x80, protocol = "official") {
13721430
sessionStorage.removeItem('webusb')
13731431
if (protocol == "official") {
@@ -1892,5 +1950,6 @@ export {
18921950
readPacketNoVerify,
18931951
uve5_flashFirmware,
18941952
sendSMSPacket,
1895-
readSMSPacket
1896-
}
1953+
readSMSPacket,
1954+
rtc_sync_beijing
1955+
}

src/views/list/sat2/index.vue

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,16 @@
6868
<script lang="ts" setup>
6969
import { ref, reactive, nextTick, onMounted, onUnmounted, computed } from 'vue';
7070
import { useAppStore } from '@/store';
71-
import { eeprom_write, eeprom_reboot, eeprom_init, hexReverseStringToUint8Array, stringToUint8Array } from '@/utils/serial.js';
71+
import { eeprom_write, eeprom_reboot, eeprom_init, hexReverseStringToUint8Array, stringToUint8Array, shared_read, shared_write } from '@/utils/serial.js';
7272
import useLoading from '@/hooks/loading';
7373
import QRCode from 'qrcode';
7474
import { Input, Select } from 'tdesign-vue-next';
7575
import { Message } from '@arco-design/web-vue';
7676
77+
// Must match ESP32 mapping in src/app/driver/eeprom.cpp
78+
// EEPROM 0x1E200..0x20000 -> shared offset 0x10000..
79+
const UVE5_SHARED_TLE_BASE = 0x10000;
80+
7781
const { loading, setLoading } = useLoading(true);
7882
7983
const appStore = useAppStore();
@@ -142,6 +146,48 @@ const state: {
142146
satsData: []
143147
})
144148
149+
// Auto-detect UVE5 shared-partition mode.
150+
// For UVE5 we always store TLE in the ESP32 shared partition (mapped from logical EEPROM 0x1E200..0x20000).
151+
// This is derived from the handshake firmware version at connect time.
152+
const isUveDevice = computed(() => appStore.isUve5);
153+
154+
const uint8ToAscii = (bytes: Uint8Array) => {
155+
// Keep it simple: the payload is ASCII. Replace NUL with spaces for readability.
156+
const text = new TextDecoder('utf-8', { fatal: false }).decode(bytes);
157+
return text.replace(/\u0000/g, ' ');
158+
}
159+
160+
const readSharedBytes = async (start: number, length: number) => {
161+
const out = new Uint8Array(length);
162+
for (let i = 0; i < length; i += 0x80) {
163+
const chunkLen = Math.min(0x80, length - i);
164+
const chunk = await shared_read(appStore.connectPort, start + i, chunkLen);
165+
out.set(chunk.subarray(0, chunkLen), i);
166+
}
167+
return out;
168+
}
169+
170+
const validateTleRecordBytes = (record: Uint8Array) => {
171+
if (!record || record.length < 160) return { ok: false, reason: 'readback too short' };
172+
const nameBytes = record.subarray(0, 9);
173+
const line1Bytes = record.subarray(9, 9 + 69);
174+
const line2Bytes = record.subarray(9 + 69, 9 + 69 + 69);
175+
176+
const allSame = (bytes: Uint8Array, value: number) => bytes.every((b) => b === value);
177+
if (allSame(record, 0x00) || allSame(record, 0xff)) return { ok: false, reason: 'all 0x00/0xFF' };
178+
179+
const name = uint8ToAscii(nameBytes).trim();
180+
const line1 = uint8ToAscii(line1Bytes);
181+
const line2 = uint8ToAscii(line2Bytes);
182+
183+
// Minimal structure check; firmware parser is stricter.
184+
const looksLikeTle = line1.startsWith('1 ') && line2.startsWith('2 ');
185+
if (!looksLikeTle) {
186+
return { ok: false, reason: `unexpected TLE header (name='${name}')` };
187+
}
188+
return { ok: true, reason: '', name, line1, line2 };
189+
}
190+
145191
const editableCellState = (cellParams) => {
146192
// 第一行不允许编辑
147193
const { row } = cellParams;
@@ -432,6 +478,20 @@ const restoreRange = async (start: any = 0, uint8Array: any) => {
432478
state.status = state.status + "写入进度:100.0%<br/>";
433479
}
434480
481+
const restoreRangeShared = async (start: number, uint8Array: Uint8Array) => {
482+
await eeprom_init(appStore.connectPort);
483+
for (let i = 0; i < uint8Array.length; i += 0x40) {
484+
const chunk = uint8Array.slice(i, i + 0x40);
485+
await shared_write(appStore.connectPort, start + i, chunk, chunk.length);
486+
state.status = state.status + "写入进度:" + ((i / uint8Array.length) * 100).toFixed(1) + "%<br/>";
487+
nextTick(() => {
488+
const textarea = document?.getElementById('statusArea');
489+
if (textarea) textarea.scrollTop = textarea?.scrollHeight;
490+
})
491+
}
492+
state.status = state.status + "写入进度:100.0%<br/>";
493+
}
494+
435495
const calculateChecksum = (line: string) => {
436496
const chars = line.replace(/\d$/, ''); // 移除末位校验和
437497
let sum = 0;
@@ -450,7 +510,7 @@ const validateChecksum = (line: string) => {
450510
451511
const writeIt = async () => {
452512
if (appStore.connectState != true) { alert(sessionStorage.getItem('noticeConnectK5')); return; };
453-
if(appStore.configuration?.sat2 != true){
513+
if (!isUveDevice.value && appStore.configuration?.sat2 != true) {
454514
alert(sessionStorage.getItem('noticeVersionNoSupport'));
455515
return;
456516
}
@@ -501,8 +561,33 @@ const writeIt = async () => {
501561
duration: 10 * 1000,
502562
});
503563
}
504-
await restoreRange(0x1E200, payload)
505-
await syncTime()
564+
if (isUveDevice.value) {
565+
state.status += `检测到 UVE 设备:将写入 shared@0x${UVE5_SHARED_TLE_BASE.toString(16)}<br/>`;
566+
await restoreRangeShared(UVE5_SHARED_TLE_BASE, payload)
567+
568+
// Read-back verify first record so users immediately know whether the shared write actually landed.
569+
try {
570+
const first = await readSharedBytes(UVE5_SHARED_TLE_BASE, 160);
571+
const chk = validateTleRecordBytes(first);
572+
if (!chk.ok) {
573+
setLoading(false)
574+
return Message.error({
575+
content: `写入完成但读回校验失败:${chk.reason}。这通常表示 shared 分区不可用(分区表没刷/label 不匹配)或固件未支持 shared 读写命令。\n\n如果你是用 app0_main 的快速上传刷机:请确保 partitions.bin 也被写入(本仓库 scripts/upload_app0.py 已改为同时刷 partitions.bin@0x8000)。或者完整刷一次 factory/分区表后再重试。`,
576+
duration: 12 * 1000,
577+
});
578+
}
579+
state.status += `读回校验通过:${chk.name}<br/>`;
580+
} catch (e: any) {
581+
setLoading(false)
582+
return Message.error({
583+
content: `写入完成但读回校验异常:${e?.message ?? e}。可能固件不支持 shared_read/shared_write。`,
584+
duration: 12 * 1000,
585+
});
586+
}
587+
} else {
588+
state.status += `非 UVE 设备:将写入 EEPROM@0x1E200<br/>`;
589+
await restoreRange(0x1E200, payload)
590+
}
506591
await eeprom_reboot(appStore.connectPort);
507592
setLoading(false)
508593
}

0 commit comments

Comments
 (0)