From c2b8f6ba09bc53c6eca8f56b1eec6aac486e73d1 Mon Sep 17 00:00:00 2001 From: kerms Date: Mon, 20 May 2024 10:03:25 +0800 Subject: [PATCH 01/53] feat(fullscreen, UI), reformat code - add ws binary message communication (not stable) - reformat ws: move ws text/bin packing code to msgRouter - reformat api: centralize module ID --- README.md | 4 +- set_env.sh | 2 +- src/App.vue | 12 ++-- src/api/apiWifi.ts | 13 ++-- src/api/binDataDef.ts | 52 ++++++++++++++ src/api/index.ts | 27 ++++++-- src/assets/page.css | 7 +- src/composables/websocket/websocketWrapper.ts | 24 ++----- src/router/msgRouter.ts | 42 ++++++++++-- src/views/About.vue | 24 +++---- src/views/Wifi.vue | 26 +++---- src/views/navigation/NavBar.vue | 67 +++++++++++++------ 12 files changed, 204 insertions(+), 96 deletions(-) create mode 100644 src/api/binDataDef.ts diff --git a/README.md b/README.md index 64ef6a2..2bfe156 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# 允斯调试器的内嵌网页版上位机 \ No newline at end of file +# 允斯无线透传器的内嵌网页版上位机 + + diff --git a/set_env.sh b/set_env.sh index 7c2198e..bf8f818 100755 --- a/set_env.sh +++ b/set_env.sh @@ -1,3 +1,3 @@ #!/bin/bash -export VITE_APP_GIT_TAG=$(git describe --tags) +export VITE_APP_GIT_TAG=$(git describe --tags | cut -d'-' -f1,2) export VITE_APP_LAST_COMMIT=$(git log -1 --format=%cd) \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 6a01979..746b801 100644 --- a/src/App.vue +++ b/src/App.vue @@ -40,7 +40,7 @@ onMounted(() => { logHelloMessage(); let host = ""; if (isDevMode()) { - host = import.meta.env.VITE_DEVICE_HOST_NAME; + host = import.meta.env.VITE_DEVICE_HOST_NAME || "dap.local"; } else { host = window.location.host } @@ -55,8 +55,10 @@ onUnmounted(() => { diff --git a/src/api/apiWifi.ts b/src/api/apiWifi.ts index a07ebf5..3866841 100644 --- a/src/api/apiWifi.ts +++ b/src/api/apiWifi.ts @@ -1,6 +1,5 @@ -import {type ApiJsonMsg, sendJsonMsg} from '@/api' +import {type ApiJsonMsg, sendJsonMsg, WtModuleID} from '@/api' -export const WifiModuleID = 1; export enum WifiCmd { UNKNOWN = 0, WIFI_API_JSON_STA_GET_AP_INFO, @@ -17,7 +16,7 @@ interface WifiMsgOut extends ApiJsonMsg { export function wifi_get_scan_list() { const msg : WifiMsgOut = { - module: WifiModuleID, + module: WtModuleID.WIFI, cmd: WifiCmd.WIFI_API_JSON_GET_SCAN, } sendJsonMsg(msg); @@ -25,7 +24,7 @@ export function wifi_get_scan_list() { export function wifi_sta_get_ap_info() { const msg : WifiMsgOut = { - module: WifiModuleID, + module: WtModuleID.WIFI, cmd: WifiCmd.WIFI_API_JSON_STA_GET_AP_INFO, } sendJsonMsg(msg); @@ -33,7 +32,7 @@ export function wifi_sta_get_ap_info() { export function wifi_ap_get_info() { const msg : WifiMsgOut = { - module: WifiModuleID, + module: WtModuleID.WIFI, cmd: WifiCmd.WIFI_API_JSON_AP_GET_INFO, } sendJsonMsg(msg); @@ -41,7 +40,7 @@ export function wifi_ap_get_info() { export function wifi_connect_to(ssid: string, password: string) { const msg: WifiMsgOut = { - module: WifiModuleID, + module: WtModuleID.WIFI, cmd: WifiCmd.WIFI_API_JSON_CONNECT, ssid: ssid, password: password, @@ -59,6 +58,6 @@ export interface WifiInfo extends ApiJsonMsg { wifiLogo?: string; } -export interface WifiList { +export interface WifiList extends ApiJsonMsg { scan_list: Array; } diff --git a/src/api/binDataDef.ts b/src/api/binDataDef.ts new file mode 100644 index 0000000..6755469 --- /dev/null +++ b/src/api/binDataDef.ts @@ -0,0 +1,52 @@ +import type {WtModuleID} from "@/api/index"; + +export enum WtDataType { + RESERVED = 0x00, + /* primitive type */ + EVENT = 0x02, + ROUTE_HDR = 0x03, + RAW_BROADCAST = 0x04, + + /* broadcast data */ + CMD_BROADCAST = 0x11, + + /* targeted data */ + RAW = 0x20, + CMD = 0x21, + RESPONSE = 0x22, + + /* standard protocols */ + PROTOBUF = 0x40, + JSON = 0x41, + MQTT = 0x42, +} + + +export interface ApiBinaryMsg { + data_type: WtDataType, + module: WtModuleID, + sub_mod: number, + payload: Uint8Array; +} + +export function decodeHeader(arrayBuffer: ArrayBuffer) : ApiBinaryMsg { + // Create a DataView to access the data in the ArrayBuffer + const dataView = new DataView(arrayBuffer); + + // Extract the data_type from the first byte + const data_type = dataView.getUint8(0) as WtDataType; + + // Extract the module_id and sub_id from the next bytes + const module = dataView.getUint8(1); + const sub_mod = dataView.getUint8(2); + + const payload = new Uint8Array(arrayBuffer.slice(4)); + + // Constructing the header object + return { + data_type, + module, + sub_mod, + payload, + }; +} \ No newline at end of file diff --git a/src/api/index.ts b/src/api/index.ts index faf7ad1..be2fa89 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,4 +1,5 @@ import {getWebsocketService} from "@/composables/websocket/websocketService"; +import type {ApiBinaryMsg} from "@/api/binDataDef"; export interface ApiJsonMsg { module: number; @@ -26,18 +27,34 @@ export interface ControlMsg { export interface ServerMsg { type: "json" | "binary" - data: ApiJsonMsg | object; + data: string | ArrayBuffer; +} + +export enum WtModuleID { + WIFI = 1, + DATA_FLOW = 2, + UART = 4, } export function sendJsonMsg(apiJsonMsg: ApiJsonMsg) { const msg: ServerMsg = { type: "json", - data: apiJsonMsg, + data: JSON.stringify(apiJsonMsg), }; getWebsocketService().send(msg); - // toServer.postMessage(msg); } -export function sendBinMsg(msg: ApiJsonMsg) { - // toServer.postMessage(JSON.stringify(msg)); +export function sendBinMsg(binMsg: ApiBinaryMsg) { + const buffer = new Uint8Array(4 + binMsg.payload.length); + buffer[0] = binMsg.data_type; + buffer[1] = binMsg.module; + buffer[2] = binMsg.sub_mod; + buffer[3] = 0; // Reserved byte + buffer.set(binMsg.payload, 4); // Append payload after header + + const msg: ServerMsg = { + type: "binary", + data: buffer, + }; + getWebsocketService().send(msg); } \ No newline at end of file diff --git a/src/assets/page.css b/src/assets/page.css index 68acde3..823a87b 100644 --- a/src/assets/page.css +++ b/src/assets/page.css @@ -1,5 +1,5 @@ .text-layout { - @apply m-auto max-w-2xl min-w-min px-2 + @apply mx-auto max-w-2xl w-full sm:min-w-[640px] px-2 } .page-title { @@ -14,3 +14,8 @@ @apply text-blue-600 font-bold; cursor: default; } + +.el-checkbox:hover { + background-color: var(--el-color-primary-light-9); + border-color: var(--el-color-primary-light-8); +} diff --git a/src/composables/websocket/websocketWrapper.ts b/src/composables/websocket/websocketWrapper.ts index ee74427..c01fae7 100644 --- a/src/composables/websocket/websocketWrapper.ts +++ b/src/composables/websocket/websocketWrapper.ts @@ -80,26 +80,13 @@ class OneTimeWebsocket implements IWebsocket { return const msg: ServerMsg = { - data: {}, + data: ev.data, type: "json", } if (typeof ev.data === "string") { - try { - msg.data = JSON.parse(ev.data) as ApiJsonMsg; - if ((msg.data as ApiJsonMsg).cmd === undefined || - (msg.data as ApiJsonMsg).module === undefined - ){ - console.log("Server msg has no cmd or module"); - return; - } - } catch (e) { - console.log(e); - return; - } + msg.type = "json" } else { msg.type = "binary"; - msg.data = ev.data; - console.log(typeof ev.data); } this.msgCallback(msg); } @@ -150,11 +137,8 @@ class OneTimeWebsocket implements IWebsocket { if (isDevMode()) { console.log('WebSocket proxies data ', msg); } - if (msg.type === "binary") { - // this.socket.send(msg.data); - } else if (msg.type === "json") { - this.socket.send(JSON.stringify(msg.data)); - } + + this.socket.send(msg.data); } clear() { diff --git a/src/router/msgRouter.ts b/src/router/msgRouter.ts index 1ff9c7d..368df05 100644 --- a/src/router/msgRouter.ts +++ b/src/router/msgRouter.ts @@ -1,9 +1,11 @@ import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api"; import {isDevMode} from "@/composables/buildMode"; +import {type ApiBinaryMsg, decodeHeader} from "@/api/binDataDef"; export interface IModuleCallback { ctrlCallback: (msg: ControlMsg) => void; - serverMsgCallback: (msg: ServerMsg) => void; + serverJsonMsgCallback: (msg: ApiJsonMsg) => void; + serverBinMsgCallback: (msg: ApiBinaryMsg) => void; } const moduleMap = new Map(); @@ -22,19 +24,47 @@ export function unregisterModule(moduleId: number) { } export function routeModuleServerMsg(msg: ServerMsg) { - if (msg.type == "json") { - const module = (msg.data as ApiJsonMsg).module; + if (msg.type === "json") { + let jsonMsg: ApiJsonMsg; + try { + jsonMsg = JSON.parse(msg.data as string) as ApiJsonMsg; + if (jsonMsg.cmd === undefined || + jsonMsg.module === undefined + ){ + console.log("Server msg has no cmd or module", msg.data); + return; + } + } catch (e) { + console.log(e); + return; + } + + const module = jsonMsg.module; const moduleHandler = moduleMap.get(module); if (moduleHandler) { - moduleHandler.serverMsgCallback(msg); + moduleHandler.serverJsonMsgCallback(jsonMsg); } else { if (isDevMode()) { console.log("routeModuleServerMsg module not loaded", module); } } } else { - if (isDevMode()) { - console.log("routeModuleServerMsg ignored:", msg); + const arr = msg.data as ArrayBuffer; + if (arr.byteLength < 4) { + if (isDevMode()) { + console.log("binary message too short"); + } + return; + } + + const binaryMsg = decodeHeader(msg.data as ArrayBuffer); + const moduleHandler = moduleMap.get(binaryMsg.module); + if (moduleHandler) { + moduleHandler.serverBinMsgCallback(binaryMsg); + } else { + if (isDevMode()) { + console.log("routeModuleServerMsg ignored:", msg, binaryMsg); + } } } } diff --git a/src/views/About.vue b/src/views/About.vue index 5bbf039..1b1d149 100644 --- a/src/views/About.vue +++ b/src/views/About.vue @@ -1,6 +1,6 @@ @@ -224,9 +255,12 @@ \ No newline at end of file diff --git a/src/views/text-data-viewer/textDataViewer.vue b/src/views/text-data-viewer/textDataViewer.vue index 3435283..d9f549c 100644 --- a/src/views/text-data-viewer/textDataViewer.vue +++ b/src/views/text-data-viewer/textDataViewer.vue @@ -18,7 +18,7 @@
- + -
- add1 - add10 - add100 - add1000 - scrollToBottom + + + + + + -
+ @@ -93,7 +93,7 @@
-
+
{{ item.time }}TX-►|

{{ item.time }}未发送►|

-

@@ -164,6 +163,18 @@
+
+
+ + + +

+
+
+

+
+
+
@@ -196,14 +207,14 @@
- + - {{ showTxTotalByte ? `TX统计:${store.TxTotalByteCount}B` : `上个TX帧:${store.TxByteCount}B` }} + {{ `TX:${store.TxByteCount}B/${store.TxTotalByteCount}B` }} - + - {{ showRxTotalByte ? `RX统计:${store.RxTotalByteCount}B` : `上个RX帧:${store.RxByteCount}B` }} + {{ `RX:${store.RxByteCount}B/${store.RxTotalByteCount}B` }}
@@ -238,10 +249,9 @@ import {useDataViewerStore} from "@/stores/dataViewerStore"; import InlineSvg from "@/components/InlineSvg.vue"; import TextDataConfig from "@/views/text-data-viewer/textDataConfig.vue"; import {debouncedWatch} from "@vueuse/core"; +import {globalNotify} from "@/composables/notification"; const count = ref(0); -const showTxTotalByte = ref(false); -const showRxTotalByte = ref(false); const vuetifyVirtualScrollBarRef = ref(document.body); const vuetifyVirtualScrollContainerRef = ref(document.body); @@ -423,6 +433,9 @@ const handleScroll = (ev: Event) => { if (vuetifyVirtualScrollBarRef.value.scrollTop - lastScrollHeight < 0) { store.forceToBottom = false; } + } else if ((vuetifyVirtualScrollBarRef.value.scrollHeight - + vuetifyVirtualScrollBarRef.value.scrollTop) <= vuetifyVirtualScrollBarRef.value.clientHeight) { + store.forceToBottom = true; } lastScrollHeight = vuetifyVirtualScrollBarRef.value.scrollTop; }; @@ -444,6 +457,11 @@ function handleTextboxKeydown(ev: KeyboardEvent) { } function onSendClick() { + if (!uartInputTextBox.value) { + globalNotify("发送框无数据发送") + return; + } + if (store.acceptIncomingData) { if (isSendTextFormat.value) { store.addString(uartInputTextBox.value, false, true); From f0f11c0646f91d7fc639ea476e77625356cb5485 Mon Sep 17 00:00:00 2001 From: kerms Date: Sat, 25 May 2024 09:38:54 +0800 Subject: [PATCH 06/53] fix(data-viewer): correct text line break in presence of --- src/stores/dataViewerStore.ts | 47 ++++++++++++++----- src/views/text-data-viewer/textDataViewer.vue | 31 +++++++----- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/stores/dataViewerStore.ts b/src/stores/dataViewerStore.ts index 9e90da1..be502ee 100644 --- a/src/stores/dataViewerStore.ts +++ b/src/stores/dataViewerStore.ts @@ -137,7 +137,7 @@ function u8toHexdump(buffer: Uint8Array) { function strToHTML(str: string) { return str.replace(/\n/g, '
') // Replace newline with
tag .replace(/\t/g, ' ') // Replace tab with spaces (or you could use ' ' for single spaces) - .replace(/ /g, ' '); + .replace(/ /g, ' '); } function isArrayContained(subArray: Uint8Array, mainArray: Uint8Array, @@ -376,11 +376,15 @@ export const useDataViewerStore = defineStore('text-viewer', () => { } else { RxSegment = new Uint8Array(); } - RxRemainHexdump.value = u8toHexdump(RxSegment); for (let i = 0; i < frames.length; i++) { addItem(frames[i], isRX, doSend); } + + if (RxSegment.length > 8192) { + addItem(RxSegment, isRX, doSend); + RxSegment = new Uint8Array(); + } } const frameBreakRet = { @@ -409,6 +413,11 @@ export const useDataViewerStore = defineStore('text-viewer', () => { const TxTotalByteCount = ref(0); const TxByteCount = ref(0); + let TxByteCountLocal = 0; + let TxTotalByteCountLocal = 0; + let RxByteCountLocal = 0; + let RxTotalByteCountLocal = 0; + const enableFilter = ref(true); const forceToBottom = ref(true); const filterChanged = ref(false); @@ -511,9 +520,19 @@ export const useDataViewerStore = defineStore('text-viewer', () => { } }, {immediate: true}); + /* delayed value update, prevent quick unnecessary update */ setInterval(() => { dataBufLength.value = dataBuf.length; - }, 500); + TxByteCount.value = TxByteCountLocal; + TxTotalByteCount.value = TxTotalByteCountLocal; + RxByteCount.value = RxByteCountLocal; + RxTotalByteCount.value = RxTotalByteCountLocal; + if (RxSegment.length) { + RxRemainHexdump.value = u8toHexdump(RxSegment); + } else { + RxRemainHexdump.value = ""; + } + }, 200); function addString(item: string, isRX: boolean = false, doSend: boolean = false, type: number = 0) { const encoder = new TextEncoder(); @@ -594,8 +613,8 @@ export const useDataViewerStore = defineStore('text-viewer', () => { // }); if (isRX) { - RxTotalByteCount.value += item.length; - RxByteCount.value = item.length; + RxTotalByteCountLocal += item.length; + RxByteCountLocal = item.length; } else { /* append prefix and suffix */ if (computedPrefixValue.value.length || computedSuffixValue.value.length) { @@ -614,8 +633,8 @@ export const useDataViewerStore = defineStore('text-viewer', () => { } else { type = 1; } - TxTotalByteCount.value += item.length; - TxByteCount.value = item.length; + TxTotalByteCountLocal += item.length; + TxByteCountLocal = item.length; } let str = "" @@ -722,9 +741,9 @@ export const useDataViewerStore = defineStore('text-viewer', () => { function clearByteCount(isRX: boolean) { if (isRX) { - RxTotalByteCount.value = 0; + RxTotalByteCountLocal = 0; } else { - TxTotalByteCount.value = 0; + TxTotalByteCountLocal = 0; } } @@ -734,11 +753,13 @@ export const useDataViewerStore = defineStore('text-viewer', () => { dataBufLength.value = 0; batchStartIndex = 0; - RxByteCount.value = 0; - RxTotalByteCount.value = 0; + RxByteCountLocal = 0; + RxTotalByteCountLocal = 0; + TxByteCountLocal = 0; + TxTotalByteCountLocal = 0; - TxByteCount.value = 0; - TxTotalByteCount.value = 0; + RxSegment = new Uint8Array(); + RxRemainHexdump.value = ""; } function clearFilteredBuff() { diff --git a/src/views/text-data-viewer/textDataViewer.vue b/src/views/text-data-viewer/textDataViewer.vue index d9f549c..ce20cc9 100644 --- a/src/views/text-data-viewer/textDataViewer.vue +++ b/src/views/text-data-viewer/textDataViewer.vue @@ -103,8 +103,8 @@ :class="[store.enableLineWrap ? 'break-all' : 'text-nowrap']" >