1
0
Fork 0
mirror of https://github.com/silenty4ng/k5web synced 2025-04-19 08:39:56 +00:00
This commit is contained in:
Silent YANG 2024-01-27 01:23:54 +08:00
parent 46e7bff599
commit 9a805143c6
5 changed files with 184 additions and 53 deletions
src
components/navbar
router/routes/modules
utils
views
dashboard/workplace/components
list/card

View file

@ -81,7 +81,7 @@
import useLocale from '@/hooks/locale';
import useUser from '@/hooks/user';
import Menu from '@/components/menu/index.vue';
import { connect, disconnect, sendPacket, readPacket } from '@/utils/serial.js';
import { connect, disconnect, eeprom_init } from '@/utils/serial.js';
const drivers = import.meta.glob('@/drivers/*.json', { eager: true });
const appStore = useAppStore();
@ -168,15 +168,6 @@
appStore.updateSettings({ connectState: false, connectPort: null, firmwareVersion: "" });
}
}
const eeprom_init = async (port: any) => {
const packet = new Uint8Array([0x14, 0x05, 0x04, 0x00, 0xff, 0xff, 0xff, 0xff]);
await sendPacket(port, packet);
const response = await readPacket(port, 0x15);
const decoder = new TextDecoder();
const version = new Uint8Array(response.slice(4, 4+16));
return decoder.decode(version.slice(0, version.indexOf(0)));
}
</script>
<style scoped lang="less">

View file

@ -12,22 +12,22 @@ const LIST: AppRouteRecordRaw = {
order: 2,
},
children: [
{
path: 'search-table', // The midline path complies with SEO specifications
name: 'SearchTable',
component: () => import('@/views/list/search-table/index.vue'),
meta: {
locale: 'menu.list.searchTable',
requiresAuth: true,
roles: ['*'],
},
},
// {
// path: 'search-table', // The midline path complies with SEO specifications
// name: 'SearchTable',
// component: () => import('@/views/list/search-table/index.vue'),
// meta: {
// locale: 'menu.list.searchTable',
// requiresAuth: true,
// roles: ['*'],
// },
// },
{
path: 'card',
name: 'Card',
component: () => import('@/views/list/card/index.vue'),
meta: {
locale: 'menu.list.cardList',
locale: '备份/还原',
requiresAuth: true,
roles: ['*'],
},
@ -35,4 +35,4 @@ const LIST: AppRouteRecordRaw = {
],
};
// export default LIST;
export default LIST;

View file

@ -240,4 +240,69 @@ function uint8ArrayToHexString(uint8Array) {
.join('');
}
export { connect, disconnect, sendPacket, readPacket, uint8ArrayToHexString }
async function eeprom_init(port) {
const packet = new Uint8Array([0x14, 0x05, 0x04, 0x00, 0xff, 0xff, 0xff, 0xff]);
await sendPacket(port, packet);
const response = await readPacket(port, 0x15);
const decoder = new TextDecoder();
const version = new Uint8Array(response.slice(4, 4+16));
return decoder.decode(version.slice(0, version.indexOf(0)));
}
async function eeprom_read(port, address, size = 0x80) {
// packet format: uint16 ID, uint16 length, uint16 address, uint8 size, uint8 padding, uint32 timestamp
// size can be up to 0x80 bytes
const address_msb = (address & 0xff00) >> 8;
const address_lsb = address & 0xff;
const address_msb_h = (address & 0xff000000) >> 24;
const address_lsb_h = (address & 0xff0000) >> 16;
const packet = new Uint8Array([0x2b, 0x05, 0x08, 0x00, address_lsb_h, address_msb_h, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, address_lsb, address_msb]);
await sendPacket(port, packet);
const response = await readPacket(port, 0x1c);
// reply format: uint16 ID, uint16 length, uint16 offset, uint8 size, uint8 padding, uint8[128] data
// extract data from response using size
if (response[6] !== size) {
throw ('eeprom read reply has wrong size.');
}
const data = new Uint8Array(response.slice(8));
return data;
}
async function eeprom_write(port, address, input, size = 0x80){
// packet format: uint16 ID, uint16 length, uint16 address, uint8 size, uint8 padding, uint32 timestamp
// size can be up to 0x80 bytes
const address_msb = (address & 0xff00) >> 8;
const address_lsb = address & 0xff;
const address_msb_h = (address & 0xff000000) >> 24;
const address_lsb_h = (address & 0xff0000) >> 16;
const packet = new Uint8Array([0x38, 0x05, 0x1c, 0x00, address_lsb_h, address_msb_h, 0x82, 0x00, 0xff, 0xff, 0xff, 0xff, address_lsb, address_msb]);
const mergedArray = new Uint8Array(packet.length + input.length);
mergedArray.set(packet);
mergedArray.set(input, packet.length);
await sendPacket(port, mergedArray);
const response = await readPacket(port, 0x1e);
return true;
}
async function eeprom_reboot(port) {
// packet format: uint16 ID
const packet = new Uint8Array([0xdd, 0x05]);
await sendPacket(port, packet);
return true;
}
async function check_eeprom(port){
alert('TODO')
return null;
}
export { connect, disconnect, sendPacket, readPacket, uint8ArrayToHexString, eeprom_init, eeprom_read, eeprom_reboot, check_eeprom, eeprom_write }

View file

@ -8,15 +8,25 @@
<a-divider class="panel-border" />
<a-card v-show="appStore.connectState" :style="{ width: '360px', marginTop: '2em', marginBottom: '2em' }" title="手台信息">
当前固件版本{{ appStore.firmwareVersion }} <br />
匹配写频配置{{ appStore.configuration?.name }}
匹配写频配置{{ appStore.configuration?.name }} <br />
存储大小{{ state.eepromSize }} <a-button size="mini" type="primary" @click="checkEeprom">检测</a-button>
</a-card>
</a-col>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import { useAppStore } from '@/store';
import { eeprom_read } from '@/utils/serial.js';
const appStore = useAppStore();
const state = reactive({
eepromSize: "点击检测按钮检测"
})
const checkEeprom = async () => {
}
</script>
<style scoped lang="less">

View file

@ -1,33 +1,22 @@
<template>
<div class="container">
<Breadcrumb :items="['menu.list', 'menu.list.cardList']" />
<Breadcrumb :items="['小工具', '备份/还原']" />
<a-row :gutter="20" align="stretch">
<a-col :span="24">
<a-card class="general-card" :title="$t('menu.list.cardList')">
<a-row justify="space-between">
<a-col :span="24">
<a-tabs :default-active-tab="1" type="rounded">
<a-tab-pane key="1" :title="$t('cardList.tab.title.all')">
<QualityInspection />
<TheService />
<RulesPreset />
</a-tab-pane>
<a-tab-pane key="2" :title="$t('cardList.tab.title.content')">
<QualityInspection />
</a-tab-pane>
<a-tab-pane key="3" :title="$t('cardList.tab.title.service')">
<TheService />
</a-tab-pane>
<a-tab-pane key="4" :title="$t('cardList.tab.title.preset')">
<RulesPreset />
</a-tab-pane>
</a-tabs>
</a-col>
<a-input-search
:placeholder="$t('cardList.searchInput.placeholder')"
style="width: 240px; position: absolute; top: 60px; right: 20px"
/>
</a-row>
<a-card class="general-card" title="备份/还原">
<a-space>
<a-button type="primary" @click="backup">备份</a-button>
<a-button @click="restore">恢复</a-button>
<a-select v-model="state.eepromType" :style="{width:'320px'}" placeholder="选择EEPROM大小">
<a-option value="1">8KB64Kbit</a-option>
<a-option value="2">128KB1Mbit</a-option>
<a-option value="3">256KB2Mbit</a-option>
<a-option value="4">512KB4Mbit</a-option>
</a-select>
<a-button type="text" @click="checkEeprom">自动检测</a-button>
</a-space>
<a-divider />
<div id="statusArea" style="height: 20em; background-color: azure; color: silver; overflow: auto; padding: 20px" v-html="state.status"></div>
</a-card>
</a-col>
</a-row>
@ -35,9 +24,85 @@
</template>
<script lang="ts" setup>
import QualityInspection from './components/quality-inspection.vue';
import TheService from './components/the-service.vue';
import RulesPreset from './components/rules-preset.vue';
import { reactive, nextTick } from 'vue';
import { useAppStore } from '@/store';
import { check_eeprom, eeprom_read, eeprom_write, eeprom_reboot } from '@/utils/serial.js';
const appStore = useAppStore();
const state = reactive({
status: "点击备份按钮将生成EEPROM备份文件<br/><br/>",
eepromType: ""
})
const checkEeprom = async () => {
await check_eeprom();
}
const backup = async() => {
let _max = 0x2000;
switch(state.eepromType){
case "1":
_max = 0x2000;
break;
case "2":
_max = 0x20000;
break;
case "3":
_max = 0x40000;
break;
case "4":
_max = 0x80000;
break;
default:
_max = 0x2000;
}
let rawEEPROM = new Uint8Array(_max);
for (let i = 0; i < _max; i += 0x80) {
const data = await eeprom_read(appStore.connectPort, i);
rawEEPROM.set(data, i);
state.status = state.status + "备份进度:" + ((i / _max) * 100).toFixed(1) + "%<br/>";
nextTick(()=>{
const textarea = document?.getElementById('statusArea');
if(textarea)textarea.scrollTop = textarea?.scrollHeight;
})
}
state.status = state.status + "备份进度100%<br/>";
nextTick(()=>{
const textarea = document?.getElementById('statusArea');
if(textarea)textarea.scrollTop = textarea?.scrollHeight;
})
console.log(rawEEPROM);
const blob = new Blob([rawEEPROM], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = new Date() + '_backup.bin';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
const restore = async() => {
const input = document.createElement('input');
input.type = 'file';
input.onchange = async() => {
const blob = new Blob([input.files[0]], {type: 'application/octet-stream' });
const rawEEPROM = new Uint8Array(await blob.arrayBuffer());
for (let i = 0; i < input.files[0].size; i += 0x80) {
await eeprom_write(appStore.connectPort, i, rawEEPROM.slice(i, i + 0x80));
state.status = state.status + "恢复进度:" + ((i / input.files[0].size) * 100).toFixed(1) + "%<br/>";
nextTick(()=>{
const textarea = document?.getElementById('statusArea');
if(textarea)textarea.scrollTop = textarea?.scrollHeight;
})
}
state.status = state.status + "恢复进度100%<br/>";
eeprom_reboot(appStore.connectPort);
};
input.click();
}
</script>
<script lang="ts">