├── .dockerignore ├── .editorconfig ├── .env ├── .eslintrc.js ├── .github └── workflows │ ├── docker-publish.yml │ ├── go.yml │ ├── heroku.yml │ ├── node.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── babel.config.js ├── build.ps1 ├── build.sh ├── docs ├── development.md ├── dir_struct.md ├── protocol_http.md ├── protocol_serial.md └── protocol_tcp.md ├── go.mod ├── go.sum ├── internal ├── helper │ ├── download.go │ └── helper.go ├── option │ ├── option.go │ └── option_test.go ├── serial │ ├── serial.go │ └── testport.go ├── server │ ├── ctrler_test.go │ ├── filectl.go │ ├── optionctrl.go │ ├── optionctrl_test.go │ ├── serialctrl.go │ ├── serialctrl_test.go │ ├── server.go │ ├── variablectrl.go │ ├── variablectrl_test.go │ └── websocket.go └── variable │ ├── chart.go │ ├── const.go │ ├── cvttype.go │ ├── cvttype_test.go │ ├── datautil.go │ ├── datautil_test.go │ ├── filt.go │ ├── filt_test.go │ ├── opt_json.go │ ├── proj.go │ ├── read_write.go │ └── variable.go ├── log ├── asuwave.exe.LAPTOP-KLOCKHJ1.LAPTOP-KLOCKHJ1_bige.log.INFO.20220516-204625.8312 └── asuwave.exe.LAPTOP-KLOCKHJ1.LAPTOP-KLOCKHJ1_bige.log.INFO.20220516-204713.9876 ├── main.go ├── main_dev.go ├── main_release.go ├── mcu ├── SerialLineIP.cpp ├── SerialLineIP.hpp ├── asuwave.cpp └── asuwave.hpp ├── package-lock.json ├── package.json ├── pic ├── IMG_0095.png └── IMG_0096.png ├── pkg ├── elffile │ ├── elffile.go │ └── watch.go ├── jsonfile │ └── jsonfile.go └── slip │ ├── slip.go │ └── slip_test.go ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── file.js │ ├── internal.js │ ├── option.js │ ├── serial.js │ ├── variable.js │ └── version.js ├── assets │ └── logo.png ├── components │ ├── AboutDialog │ │ ├── AboutDialog.vue │ │ ├── AboutOption.vue │ │ └── AboutVersion.vue │ ├── ChartCard.vue │ ├── DrawerList.vue │ ├── ErrorAlert.vue │ ├── SerialPort.vue │ ├── VariableAllDialog.vue │ ├── VariableNewDialog.vue │ ├── VariableReadList.vue │ └── VariableWriteList.vue ├── const │ └── BaudRate.json ├── main.js ├── mixins │ └── errorMixin.js ├── plugins │ └── vuetify.js └── store │ ├── index.js │ └── modules │ ├── File.js │ ├── Option.js │ ├── SerialPort.js │ ├── Variables.js │ └── Version.js └── vue.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.log 3 | /asuwave 4 | /build 5 | config.json 6 | vToRead.json 7 | vToWrite.json 8 | vToProj.json 9 | bindata.go 10 | 11 | .DS_Store 12 | node_modules 13 | /dist 14 | 15 | 16 | # local env files 17 | .env.local 18 | .env.*.local 19 | 20 | # Log files 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | pnpm-debug.log* 25 | 26 | # Editor directories and files 27 | .idea 28 | .vscode 29 | *.suo 30 | *.ntvs* 31 | *.njsproj 32 | *.sln 33 | *.sw? 34 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*] 8 | charset = utf-8 9 | 10 | [*.{js,vue,html}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | VUE_APP_GITTAG=v0.10.2 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | // add more generic rulesets here, such as: 4 | // 'plugin:vue/vue3-recommended', 5 | 'plugin:vue/recommended', // Use this if you are using Vue.js 2.x. 6 | ], 7 | rules: { 8 | "vue/max-attributes-per-line": ["error", { 9 | "singleline": { 10 | "max": 3 11 | }, 12 | "multiline": { 13 | "max": 3 14 | } 15 | }], 16 | "indent": ["error", 2] 17 | } 18 | } -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | push: 10 | branches: [ main ] 11 | # Publish semver tags as releases. 12 | tags: [ 'v*.*.*' ] 13 | pull_request: 14 | branches: [ main ] 15 | 16 | env: 17 | # Use docker.io for Docker Hub if empty 18 | REGISTRY: ghcr.io 19 | # github.repository as / 20 | IMAGE_NAME: ${{ github.repository }} 21 | 22 | 23 | jobs: 24 | build: 25 | 26 | runs-on: ubuntu-latest 27 | permissions: 28 | contents: read 29 | packages: write 30 | # This is used to complete the identity challenge 31 | # with sigstore/fulcio when running outside of PRs. 32 | id-token: write 33 | 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v3 37 | 38 | # Install the cosign tool except on PR 39 | # https://github.com/sigstore/cosign-installer 40 | - name: Install cosign 41 | if: github.event_name != 'pull_request' 42 | uses: sigstore/cosign-installer@d6a3abf1bdea83574e28d40543793018b6035605 43 | with: 44 | cosign-release: 'v1.7.1' 45 | 46 | 47 | # Workaround: https://github.com/docker/build-push-action/issues/461 48 | - name: Setup Docker buildx 49 | uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf 50 | 51 | # Login against a Docker registry except on PR 52 | # https://github.com/docker/login-action 53 | - name: Log into registry ${{ env.REGISTRY }} 54 | if: github.event_name != 'pull_request' 55 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 56 | with: 57 | registry: ${{ env.REGISTRY }} 58 | username: ${{ github.actor }} 59 | password: ${{ secrets.GITHUB_TOKEN }} 60 | 61 | # Extract metadata (tags, labels) for Docker 62 | # https://github.com/docker/metadata-action 63 | - name: Extract Docker metadata 64 | id: meta 65 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 66 | with: 67 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 68 | 69 | # Build and push Docker image with Buildx (don't push on PR) 70 | # https://github.com/docker/build-push-action 71 | - name: Build and push Docker image 72 | id: build-and-push 73 | uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a 74 | with: 75 | context: . 76 | push: ${{ github.event_name != 'pull_request' }} 77 | tags: ${{ steps.meta.outputs.tags }} 78 | labels: ${{ steps.meta.outputs.labels }} 79 | 80 | # Sign the resulting Docker image digest except on PRs. 81 | # This will only write to the public Rekor transparency log when the Docker 82 | # repository is public to avoid leaking data. If you would like to publish 83 | # transparency data even for private images, pass --force to cosign below. 84 | # https://github.com/sigstore/cosign 85 | - name: Sign the published Docker image 86 | if: ${{ github.event_name != 'pull_request' }} 87 | env: 88 | COSIGN_EXPERIMENTAL: "true" 89 | # This step uses the identity token to provision an ephemeral certificate 90 | # against the sigstore community Fulcio instance. 91 | run: cosign sign ${{ steps.meta.outputs.tags }}@${{ steps.build-and-push.outputs.digest }} 92 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Setup go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.17 20 | 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v2 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /.github/workflows/heroku.yml: -------------------------------------------------------------------------------- 1 | name: Heroku 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | 14 | - name: Build, Push and Deploy to Heroku 15 | id: heroku 16 | uses: jctaveras/heroku-deploy@v2.1.3 17 | with: 18 | email: ${{ secrets.HEROKU_EMAIL }} 19 | api_key: ${{ secrets.HEROKU_API_KEY }} 20 | app_name: ${{ secrets.HEROKU_APP_NAME }} 21 | dockerfile_path: './' 22 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | name: Node.js 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [12.x, 14.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - run: npm run build --if-present 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | tags: 7 | - 'v*' 8 | pull_request: 9 | branches: [ main ] 10 | 11 | jobs: 12 | build: 13 | name: Build 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | - name: Set up Go 1.x 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: ^1.17 21 | id: go 22 | 23 | - name: Set up node 14.x 24 | uses: actions/setup-node@v2 25 | with: 26 | node-version: 14.x 27 | id: node 28 | 29 | - name: Checkout code 30 | uses: actions/checkout@v3 31 | with: 32 | fetch-depth: 0 33 | 34 | 35 | - name: Get git tag 36 | uses: olegtarasov/get-tag@v2.1 37 | id: tagName 38 | 39 | - name: Build project 40 | run: | 41 | chmod +x build.sh 42 | ./build.sh ${{ steps.tagName.outputs.tag }} 43 | 44 | - name: Upload 45 | uses: actions/upload-artifact@v2 46 | with: 47 | name: my-artifact 48 | path: | 49 | ./build/*.zip 50 | 51 | release: 52 | name: Release 53 | needs: build 54 | runs-on: ubuntu-latest 55 | if: github.event_name == 'push' && contains(github.ref, 'refs/tags/v') 56 | steps: 57 | 58 | - name: Checkout code 59 | uses: actions/checkout@v2 60 | 61 | - name: Set output 62 | id: vars 63 | run: echo ::set-output name=tag::${GITHUB_REF#refs/*/} 64 | 65 | - name: Download 66 | uses: actions/download-artifact@v2 67 | with: 68 | name: my-artifact 69 | path: ./build/ 70 | 71 | - name: Release 72 | uses: softprops/action-gh-release@v1 73 | if: startsWith(github.ref, 'refs/tags/') 74 | with: 75 | files: | 76 | ./build/asuwave_${{ steps.vars.outputs.tag }}_darwin_amd64.zip 77 | ./build/asuwave_${{ steps.vars.outputs.tag }}_darwin_arm64.zip 78 | ./build/asuwave_${{ steps.vars.outputs.tag }}_linux_amd64.zip 79 | ./build/asuwave_${{ steps.vars.outputs.tag }}_linux_arm64.zip 80 | ./build/asuwave_${{ steps.vars.outputs.tag }}_windows_amd64.zip 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.log 3 | /asuwave 4 | /build 5 | config.json 6 | vToRead.json 7 | vToWrite.json 8 | vToProj.json 9 | bindata.go 10 | tmp 11 | 12 | .DS_Store 13 | node_modules 14 | /dist 15 | 16 | 17 | # local env files 18 | .env.local 19 | .env.*.local 20 | 21 | # Log files 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | pnpm-debug.log* 26 | log 27 | 28 | # Editor directories and files 29 | .idea 30 | .vscode 31 | *.suo 32 | *.ntvs* 33 | *.njsproj 34 | *.sln 35 | *.sw? 36 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": false, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "arrowParens": "always", 10 | "endOfLine": "lf" 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts AS build-env 2 | WORKDIR /app 3 | COPY ./package.json ./package-lock.json /app/ 4 | RUN npm ci 5 | 6 | FROM build-env AS build 7 | COPY ./babel.config.js ./vue.config.js ./.eslintrc.js /app/ 8 | COPY ./public /app/public/ 9 | COPY ./src /app/src/ 10 | RUN npm run build 11 | 12 | FROM golang:latest AS binary 13 | WORKDIR /build 14 | COPY ./main.go ./main_release.go ./go.sum ./go.mod /build/ 15 | COPY ./internal /build/internal/ 16 | COPY ./pkg /build/pkg/ 17 | COPY --from=build /app/dist/ /build/dist/ 18 | RUN CGO_ENABLED=0 go build -tags release -ldflags="-w -s" -o asuwave 19 | 20 | FROM alpine:latest 21 | COPY --from=binary /build/asuwave /app/ 22 | 23 | CMD ["/app/asuwave"] 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 上位机 · 网页版 · 重制版 2 | 3 | [![Go](https://github.com/scutrobotlab/asuwave/actions/workflows/go.yml/badge.svg)](https://github.com/scutrobotlab/asuwave/actions/workflows/go.yml) 4 | [![Node.js](https://github.com/scutrobotlab/asuwave/actions/workflows/node.yml/badge.svg)](https://github.com/scutrobotlab/asuwave/actions/workflows/node.yml) 5 | [![Heroku](https://github.com/scutrobotlab/asuwave/actions/workflows/heroku.yml/badge.svg)](https://github.com/scutrobotlab/asuwave/actions/workflows/heroku.yml) 6 | [![Docker](https://github.com/scutrobotlab/asuwave/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/scutrobotlab/asuwave/actions/workflows/docker-publish.yml) 7 | [![Release](https://github.com/scutrobotlab/asuwave/actions/workflows/release.yml/badge.svg)](https://github.com/scutrobotlab/asuwave/actions/workflows/release.yml) 8 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/scutrobotlab/asuwave)](https://github.com/scutrobotlab/asuwave/releases) 9 | [![GitHub all releases](https://img.shields.io/github/downloads/scutrobotlab/asuwave/total)](https://github.com/scutrobotlab/asuwave/releases) 10 | [![GitHub release (latest by date)](https://img.shields.io/github/downloads/scutrobotlab/asuwave/latest/total)](https://github.com/scutrobotlab/asuwave/releases) 11 | ![GitHub repo size](https://img.shields.io/github/repo-size/scutrobotlab/asuwave) 12 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/scutrobotlab/asuwave) 13 | [![Go Report Card](https://goreportcard.com/badge/github.com/scutrobotlab/asuwave)](https://goreportcard.com/report/github.com/scutrobotlab/asuwave) 14 | 15 | [Demo](https://asuwave.herokuapp.com/) 16 | 17 | >~~你所用过坠好的上位机~~ 18 | >~~简洁、优雅且好用~~ 19 | >每日一问,今日进度如何 20 | 21 | ![logo](src/assets/logo.png) 22 | 23 | ## 佛系·上位机 24 | 25 | 其一 · 于辛丑年夏 26 | 君问何项最佛系,当属网页上位机。 27 | 先由杨工做设计,后有玮文提建议。 28 | 退队人员再召集,分工明确尽全力。 29 | 年末网管协议拟,初有成效心欢喜。 30 | 世事难料众人离,一昧孤行无所依。 31 | 来年再把项目启,当年锐气远不及。 32 | 半途而废人言弃,奈何图表帧数低。 33 | 次年又把决心立,志在月底创佳绩。 34 | 后端进展颇顺利,前端不见人踪迹。 35 | 五月将至无人理,项目组员心已急。 36 | 两日奋战舞士气,何时完成仍成谜。 37 | 回首往事泪满地,此事羞与后人提。 38 | 39 | 其二 · 于壬寅年春 40 | 尘封半载无人理,提交寥寥用户稀。 41 | 功能残缺坑满地,电控组长好嫌弃。 42 | 行将毕业突起意,心血来潮征提议。 43 | 孤身作战犹可期,只怕此后又长寂。 44 | 45 | ## 使用教程 46 | 47 | ### 单片机端代码准备 48 | 49 | 复制[asuwave.c](mcu/asuwave.c)和[asuwave.h](mcu/asuwave.h)到你的工程 50 | 51 | #### 串口配置 52 | 53 | 使用STM32CubeMX的默认配置,并启用串口空闲中断和DMA 54 | 55 | #### 上位机初始化函数 56 | 57 | ```c 58 | /* 59 | * @brief 上位机初始化配置函数 60 | * @param huart 串口句柄指针 61 | * @param f 获取系统时刻(ms级)函数指针,可以是FreeRTOS的xTaskGetTickCount 62 | * @retval None 63 | */ 64 | void asuwave_init(UART_HandleTypeDef *huart, uint32_t (*f)(void)); 65 | ``` 66 | 67 | 示例: 68 | 69 | ```c 70 | asuwave_init(&huart1, xTaskGetTickCount); 71 | ``` 72 | 73 | #### 串口接收处理函数 74 | 75 | ```c 76 | /** 77 | * @brief asuwave callback. 78 | * @param data_buf: received buffer array. 79 | * @param length: the length of array. 80 | * @retval None 81 | */ 82 | uint32_t asuwave_callback(uint8_t *data_buf, uint16_t length) 83 | ``` 84 | 85 | 串口接收到后将存储接收数据的数组和接收数据的长度传给该函数处理 86 | 87 | #### 周期处理数据任务 88 | 89 | ```c 90 | /** 91 | * @brief Subscribes the variable in flash memory of the given address. 92 | * @param None 93 | * @retval None 94 | */ 95 | void asuwave_subscribe(void) 96 | ``` 97 | 98 | 示例 99 | 100 | ```c 101 | /** 102 | * @brief debug task 103 | */ 104 | void tskAsuwave(void *arg) 105 | { 106 | /* Cache for Task */ 107 | 108 | /* Pre-Load for task */ 109 | TickType_t xLastWakeTime_t; 110 | xLastWakeTime_t = xTaskGetTickCount(); 111 | 112 | /* Infinite loop */ 113 | for (;;) 114 | { 115 | /* Wait for the next cycle */ 116 | vTaskDelayUntil(&xLastWakeTime_t, 10); 117 | 118 | asuwave_subscribe(); 119 | } 120 | } 121 | ``` 122 | 123 | ### 电脑端使用指南 124 | 125 | 从[Releases](https://github.com/scutrobotlab/asuwave/releases)下载符合计算机操作系统和架构的最新版软件并运行 126 | 127 | 然后在浏览器输入地址`http://localhost:8000/` 128 | 129 | 打开后的界面: 130 | 131 | ![](pic/IMG_0095.png) 132 | 133 | 变量选择界面 134 | 135 | ![](pic/IMG_0096.png) 136 | 137 | 目前上位机可以修改的变量数量上限是所有全局变量的数量,观察的变量数量上限是10. 138 | 139 | 切记不要在关闭串口后删除变量,这个问题已经提交到github仓库的Issue了,**如果大家在使用的过程中发现这款上位机没能满足你的一些需求或者你发现了bug,欢迎你到github仓库提交问题** 140 | 141 | ### 如何DEBUG 142 | 143 | 如果你发现在观察变量列表上有变量但没有曲线,进入debug,将`asuwave.c`文件中的一个结构体变量`list_addr`加入观察,看看里面是否有对应变量的地址。 144 | 145 | ```c 146 | typedef struct 147 | { 148 | uint8_t dataNum; 149 | uint32_t addr; 150 | } list_addr_t; 151 | static list_addr_t list_addr[MAX_ADDR_NUM]; 152 | ``` 153 | 154 | 其他的一些情况可以刷新一下网页看看。 155 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | $importpath="github.com/scutrobotlab/asuwave/internal/helper" 2 | $gittag=$(git describe --tags --abbrev=0) 3 | $build_prefix="asuwave_" 4 | $os_list="linux", "darwin", "windows" 5 | $arch_list="amd64", "arm64" 6 | $flags="-w -s -X '${importpath}.GitTag=${gittag}' -X '${importpath}.GitHash=$(git describe --tags --long)' -X '${importpath}.BuildTime=$(Get-Date -Format 'yyyy-MM-dd HH:mm')' -X '${importpath}.GoVersion=$(go version)'" 7 | 8 | $env=Get-Content -path .env | % { $_ -Replace "^VUE_APP_GITTAG=.*", "VUE_APP_GITTAG=${gittag}" } 9 | Out-File -FilePath .env -InputObject ${env} -Encoding utf8 10 | 11 | npm ci 12 | npm run build 13 | 14 | $build_dir="build" 15 | if(Test-Path $build_dir){ 16 | Remove-Item -Force -Recurse $build_dir 17 | } 18 | New-Item -Name $build_dir -ItemType "directory" 19 | 20 | foreach ($os in $os_list) { 21 | foreach ($arch in $arch_list) { 22 | $suffix="" 23 | if ($os -eq "windows") { 24 | if($arch -eq "arm64"){ 25 | continue 26 | }else{ 27 | $suffix=".exe" 28 | } 29 | } 30 | $file="${build_dir}\${build_prefix}${gittag}_${os}_${arch}" 31 | $out="${build_dir}\${build_prefix}${os}_${arch}${suffix}" 32 | $Env:CGO_ENABLED=0 33 | $Env:GOOS=${os} 34 | $Env:GOARCH=${arch} 35 | go build -tags release -ldflags="$flags" -o $out 36 | Compress-Archive -CompressionLevel "Optimal" -Path $out -DestinationPath "${file}.zip" 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | importpath="github.com/scutrobotlab/asuwave/internal/helper" 4 | build_prefix="asuwave_" 5 | os_list=("linux" "darwin" "windows") 6 | arch_list=("amd64" "arm64") 7 | if [ -v $1 ]; 8 | then 9 | echo "No" 10 | gittag=`git describe --tags --abbrev=0` 11 | else 12 | echo "Yes" 13 | gittag=$1 14 | fi 15 | 16 | echo ${gittag} 17 | sed -i "s/VUE_APP_GITTAG=.*/VUE_APP_GITTAG=${gittag}/g" .env 18 | cat .env 19 | 20 | npm ci 21 | npm run build 22 | 23 | build_dir="build" 24 | 25 | if [[ ! -d $build_dir ]]; then 26 | mkdir $build_dir 27 | else 28 | rm -rf $build_dir 29 | fi 30 | 31 | flags="-w -s -X '${importpath}.GitTag=${gittag}' -X '${importpath}.GitHash=$(git describe --tags --long)' -X '${importpath}.BuildTime=$(date +'%Y-%m-%d %H:%M')' -X '${importpath}.GoVersion=$(go version)'" 32 | 33 | for os in ${os_list[@]}; do 34 | for arch in ${arch_list[@]}; do 35 | suffix="" 36 | if [ "$os" == "windows" ] ; then 37 | if [ "$arch" == "arm64" ]; then 38 | continue 39 | else 40 | suffix=".exe" 41 | fi 42 | fi 43 | file=$build_dir/$build_prefix${gittag}_${os}_${arch} 44 | out=$build_dir/$build_prefix${os}_${arch}$suffix 45 | CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} go build -tags release -ldflags="$flags" -o $out 46 | zip -j -9 $file.zip $out 47 | done 48 | done 49 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # 开发指南 2 | 3 | ## 电脑端 4 | 5 | * node --version >= v14 (建议) 6 | * go version >= 1.17 (必要) 7 | 8 | ### 常用命令 9 | ```bash 10 | # 前端 11 | npm ci # 安装依赖 12 | npm run serve # 启动并调试 13 | npm run build # 生产环境构建 14 | # 后端 15 | go test -v ./... # 测试 16 | go build # 编译开发版 17 | ./asuwave # 执行 18 | # 构建Release 19 | chmod +x build.sh 20 | ./build.sh 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/dir_struct.md: -------------------------------------------------------------------------------- 1 | # 目录结构 2 | 3 | ``` 4 | ├── .github - github actions相关 5 | │   └── workflows 6 | │   ├── codeql-analysis.yml 7 | │   ├── go.yml 8 | │   ├── heroku.yml 9 | │   ├── node.yml 10 | │   └── release.yml 11 | ├── variable - 后端·协议处理 12 | │   ├── const.go 13 | │   ├── variable.go 14 | │   └── variable_test.go 15 | ├── file - 后端·elf/axf文件处理 16 | │   └── file.go 17 | ├── helper - 后端·杂七杂八函数 18 | │   └── helper.go 19 | ├── logger - 后端·日志 20 | │   └── logger.go 21 | ├── mcu - 单片机端 22 | │   ├── asuwave.c 23 | │   └── asuwave.h 24 | ├── option - 后端·用户配置控制 25 | │   ├── jsonfile.go 26 | │   ├── option.go 27 | │   └── option_test.go 28 | ├── public - 前端·假入口 29 | │   ├── favicon.ico 30 | │   └── index.html 31 | ├── serial - 后端·串口控制 32 | │   ├── serial.go 33 | │   └── testport.go 34 | ├── server - 后端·网页接口 35 | │   ├── ctrler_test.go 36 | │   ├── optionctrl.go 37 | │   ├── optionctrl_test.go 38 | │   ├── serialctrl.go 39 | │   ├── serialctrl_test.go 40 | │   ├── server.go 41 | │   ├── variablectrl.go 42 | │   ├── variablectrl_test.go 43 | │   └── websocket.go 44 | ├── src - 前端·真入口 45 | │   ├── api 46 | │   │   ├── internal.js 47 | │   │   ├── option.js 48 | │   │   ├── serial.js 49 | │   │   └── variable.js 50 | │   ├── assets 51 | │   │   └── logo.png 52 | │   ├── components 53 | │   │   ├── AboutDialog.vue 54 | │   │   ├── ChartCard.vue 55 | │   │   ├── DrawerList.vue 56 | │   │   ├── ErrorAlert.vue 57 | │   │   ├── PanelCard.vue 58 | │   │   ├── SaveConfig.vue 59 | │   │   ├── SerialPort.vue 60 | │   │   ├── VariableAllDialog.vue 61 | │   │   ├── VariableList.vue 62 | │   │   └── VariableNewDialog.vue 63 | │   ├── mixins 64 | │   │   └── errorMixin.js 65 | │   ├── plugins 66 | │   │   └── vuetify.js 67 | │   ├── store 68 | │   │ └── index.js 69 | │   ├── App.vue 70 | │   └── main.js 71 | ├── variable - 后端·变量 72 | │   ├── typeconvert.go 73 | │   ├── typeconvert_test.go 74 | │   └── variable.go 75 | ├── .dockerignore 76 | ├── .editorconfig 77 | ├── .gitignore 78 | ├── .prettierrc - 自定义前端文件格式化规则 79 | ├── babel.config.js 80 | ├── build.ps1 - 用于构建Release的脚本(Windows) 81 | ├── build.sh - 用于构建Release的脚本(Ubuntu) 82 | ├── Dockerfile - 用于部署演示网页到Heroku的docker 83 | ├── go.mod 84 | ├── go.sum 85 | ├── LICENSE 86 | ├── main_dev.go - 后端开发版 87 | ├── main_release.go - 后端正式版 88 | ├── main.go - 后端入口 89 | ├── package-lock.json 90 | ├── package.json 91 | ├── README.md 92 | └── vue.config.js 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/protocol_http.md: -------------------------------------------------------------------------------- 1 | # 通信协议 2 | 3 | ## 前言 4 | 5 | ``` 6 | +-------------+ Serial +-------------+ TCP +-------------+ 7 | | MCU |------->| Server |------->| Client | 8 | | |<-------| Backend |<-------| Frontend | 9 | +-------------+ +-------------+ +-------------+ 10 | ``` 11 | 12 | 1. 如无特殊说明,所有请求和响应参数均为JSON格式。 13 | 2. 请求成功则状态码为2XX。 14 | 3. 请求失败状态码为4XX或5XX,具体错误信息在返回JSON的Error中。 15 | 16 | ## 1. 串口 17 | 18 | ### 1.1 获取当前可用的串口列表 19 | * 请求地址 20 | 21 | | 方法 | URL | 22 | |-------|-----------| 23 | | `GET` | `/serial` | 24 | * 请求参数 25 | 26 | 无 27 | * 响应结果 28 | 29 | | 参数 | 类型 | 说明 | 30 | |---------|--------------|---------| 31 | | Serials | array string | 串口列表 | 32 | * 调用示例 33 | 34 | 请求示例: 35 | `GET /serial` 36 | 响应示例: 37 | ``` 38 | { 39 | "Serials":[ 40 | "COM3", 41 | "COM4" 42 | ] 43 | } 44 | ``` 45 | 46 | ### 1.2 获取当前已打开的串口 47 | * 请求地址 48 | 49 | | 方法 | URL | 50 | |-------|---------------| 51 | | `GET` | `/serial_cur` | 52 | * 请求参数 53 | 54 | 无 55 | * 响应结果 56 | 57 | | 参数 | 类型 | 说明 | 58 | |--------|--------|--------| 59 | | Serial | string | 串口名 | 60 | | Baud | int | 波特率 | 61 | | Status | bool | 状态 | 62 | * 调用示例 63 | 64 | 请求示例: 65 | `GET /serial_cur` 66 | 响应示例: 67 | ```json 68 | { 69 | "Serial": "COM3", 70 | "Baud": 119200, 71 | } 72 | ``` 73 | 74 | ### 1.3 打开串口 75 | * 请求地址 76 | 77 | | 方法 | URL | 78 | |--------|---------------| 79 | | `POST` | `/serial_cur` | 80 | * 请求参数 81 | 82 | | 参数 | 类型 | 说明 | 83 | |--------|--------|-------| 84 | | Serial | string | 串口名 | 85 | | Baud | int | 波特率 | 86 | * 响应结果 87 | 88 | 无 89 | * 调用示例 90 | 91 | 请求示例: 92 | `POST /serial_cur` 93 | ```json 94 | { 95 | "Serial": "COM3", 96 | "Baud": 119200 97 | } 98 | ``` 99 | 响应示例: 100 | 无 101 | 102 | ### 1.4 关闭串口 103 | * 请求地址 104 | 105 | | 方法 | URL | 106 | |----------|----------------| 107 | | `DELETE` | `/serial_cur` | 108 | * 请求参数 109 | 110 | 无 111 | * 响应结果 112 | 113 | 无 114 | * 调用示例 115 | 116 | 请求示例: 117 | `DELETE /serial_cur` 118 | 响应示例: 119 | 无 120 | 121 | ## 2. 变量 122 | 123 | ### 2.1 获取支持的变量类型 124 | * 请求地址 125 | 126 | | 方法 | URL | 127 | |-------|------------------| 128 | | `GET` | `/variable_type` | 129 | * 请求参数 130 | 131 | 无 132 | * 响应结果 133 | 134 | | 参数 | 类型 | 说明 | 135 | |-------|--------------|------------| 136 | | Types | array string | 变量类型列表 | 137 | * 调用示例 138 | 139 | 请求示例: 140 | `GET /variable_type` 141 | 响应示例: 142 | ```json 143 | { 144 | "Types":[ 145 | "double","float","int","int16_t","int32_t","int64_t","int8_t","uint16_t","uint32_t","uint64_t","uint8_t" 146 | ] 147 | } 148 | ``` 149 | 150 | ### 2.2 获取订阅变量 151 | * 请求地址 152 | 153 | | 方法 | URL | 154 | |-------|------------------| 155 | | `GET` | `/variable_read` | 156 | * 请求参数 157 | 158 | 无 159 | * 响应结果 160 | 161 | | 参数 | 类型 | 说明 | 162 | |-------------------|--------------|---------| 163 | | Variables | array struct | 变量列表 | 164 | | Variables[].Board | int | 板子代号 | 165 | | Variables[].Name | string | 变量名 | 166 | | Variables[].Type | string | 变量类型 | 167 | | Variables[].Addr | int | 变量地址 | 168 | | Variables[].Data | float | 变量值 | 169 | * 调用示例 170 | 171 | 请求示例: 172 | `GET /variable_read` 173 | 响应示例: 174 | ```json 175 | { 176 | "Variables":[ 177 | { 178 | "Board":1, 179 | "Name":"traceme", 180 | "Type":"float", 181 | "Addr":536889920, 182 | "Data":0 183 | }, 184 | { 185 | "Board":1, 186 | "Name":"count", 187 | "Type":"int", 188 | "Addr":536890180, 189 | "Data":0 190 | } 191 | ] 192 | } 193 | ``` 194 | 195 | ### 2.3 添加订阅变量 196 | * 请求地址 197 | 198 | | 方法 | URL | 199 | |--------|------------------| 200 | | `POST` | `/variable_read` | 201 | * 请求参数 202 | 203 | | 参数 | 类型 | 说明 | 204 | |-------|--------|---------| 205 | | Board | int | 板子代号 | 206 | | Name | string | 变量名 | 207 | | Type | string | 变量类型 | 208 | | Addr | int | 变量地址 | 209 | * 响应结果 210 | 211 | 无 212 | * 调用示例 213 | 214 | 请求示例: 215 | `POST /variable_read` 216 | ```json 217 | { 218 | "Board":1, 219 | "Name":"traceme", 220 | "Type":"float", 221 | "Addr":536889920 222 | } 223 | ``` 224 | 响应示例: 225 | 无 226 | 227 | ### 2.4 删除订阅变量 228 | * 请求地址 229 | 230 | | 方法 | URL | 231 | |----------|------------------| 232 | | `DELETE` | `/variable_read` | 233 | * 请求参数 234 | 235 | | 参数 | 类型 | 说明 | 236 | |-------|--------|---------| 237 | | Board | int | 板子代号 | 238 | | Name | string | 变量名 | 239 | | Type | string | 变量类型 | 240 | | Addr | int | 变量地址 | 241 | * 响应结果 242 | 243 | 无 244 | * 调用示例 245 | 246 | 请求示例: 247 | `DELETE /variable_read` 248 | ```json 249 | { 250 | "Board":1, 251 | "Name":"traceme", 252 | "Type":"float", 253 | "Addr":536889920 254 | } 255 | ``` 256 | 响应示例: 257 | 无 258 | 259 | ### 2.5 获取调参变量 260 | * 请求地址 261 | 262 | | 方法 | URL | 263 | |-------|------------------| 264 | | `GET` | `/variable_write` | 265 | * 请求参数 266 | 267 | 无 268 | * 响应结果 269 | 270 | | 参数 | 类型 | 说明 | 271 | |-------------------|--------------|---------| 272 | | Variables | array struct | 变量列表 | 273 | | Variables[].Board | int | 板子代号 | 274 | | Variables[].Name | string | 变量名 | 275 | | Variables[].Type | string | 变量类型 | 276 | | Variables[].Addr | int | 变量地址 | 277 | | Variables[].Data | float | 变量值 | 278 | * 调用示例 279 | 280 | 请求示例: 281 | `GET /variable_write` 282 | 响应示例: 283 | ```json 284 | { 285 | "Variables":[ 286 | { 287 | "Board":1, 288 | "Name":"traceme", 289 | "Type":"float", 290 | "Addr":536889920, 291 | "Data":0 292 | }, 293 | { 294 | "Board":1, 295 | "Name":"count", 296 | "Type":"int", 297 | "Addr":536890180, 298 | "Data":0 299 | } 300 | ] 301 | } 302 | ``` 303 | 304 | ### 2.6 添加调参变量 305 | * 请求地址 306 | 307 | | 方法 | URL | 308 | |--------|------------------| 309 | | `POST` | `/variable_write` | 310 | * 请求参数 311 | 312 | | 参数 | 类型 | 说明 | 313 | |-------|--------|---------| 314 | | Board | int | 板子代号 | 315 | | Name | string | 变量名 | 316 | | Type | string | 变量类型 | 317 | | Addr | int | 变量地址 | 318 | * 响应结果 319 | 320 | 无 321 | * 调用示例 322 | 323 | 请求示例: 324 | `POST /variable_write` 325 | ```json 326 | { 327 | "Board":1, 328 | "Name":"traceme", 329 | "Type":"float", 330 | "Addr":536889920 331 | } 332 | ``` 333 | 响应示例: 334 | 无 335 | 336 | ### 2.7 修改调参变量的值 337 | * 请求地址 338 | 339 | | 方法 | URL | 340 | |-------|------------------| 341 | | `PUT` | `/variable_write` | 342 | * 请求参数 343 | 344 | | 参数 | 类型 | 说明 | 345 | |-------|--------|---------| 346 | | Board | int | 板子代号 | 347 | | Name | string | 变量名 | 348 | | Type | string | 变量类型 | 349 | | Addr | int | 变量地址 | 350 | | Data | float | 变量值 | 351 | * 响应结果 352 | 353 | 无 354 | * 调用示例 355 | 356 | 请求示例: 357 | `PUT /variable_write` 358 | ```json 359 | { 360 | "Board":1, 361 | "Name":"traceme", 362 | "Type":"float", 363 | "Addr":536889920, 364 | "Data":1.5 365 | } 366 | ``` 367 | 响应示例: 368 | 无 369 | 370 | ### 2.8 删除调参变量 371 | * 请求地址 372 | 373 | | 方法 | URL | 374 | |----------|------------------| 375 | | `DELETE` | `/variable_write` | 376 | * 请求参数 377 | 378 | | 参数 | 类型 | 说明 | 379 | |-------|--------|---------| 380 | | Board | int | 板子代号 | 381 | | Name | string | 变量名 | 382 | | Type | string | 变量类型 | 383 | | Addr | int | 变量地址 | 384 | * 响应结果 385 | 386 | 无 387 | * 调用示例 388 | 389 | 请求示例: 390 | `DELETE /variable_write` 391 | ```json 392 | { 393 | "Board":1, 394 | "Name":"traceme", 395 | "Type":"float", 396 | "Addr":536889920 397 | } 398 | ``` 399 | 响应示例: 400 | 无 401 | 402 | ### 2.9 获取工程变量 403 | * 请求地址 404 | 405 | | 方法 | URL | 406 | |-------|------------------| 407 | | `GET` | `/variable_proj` | 408 | * 请求参数 409 | 410 | 无 411 | * 响应结果 412 | 413 | | 参数 | 类型 | 说明 | 414 | |-------------------|--------------|---------| 415 | | Variables | array struct | 变量列表 | 416 | | Variables[].Name | string | 变量名 | 417 | | Variables[].Type | string | 变量类型 | 418 | | Variables[].Addr | int | 变量地址 | 419 | * 调用示例 420 | 421 | 请求示例: 422 | `GET /variable_proj` 423 | 响应示例: 424 | ```json 425 | { 426 | "Variables":[ 427 | { 428 | "Name":"traceme", 429 | "Type":"float", 430 | "Addr":536889920 431 | }, 432 | { 433 | "Name":"count", 434 | "Type":"int", 435 | "Addr":536890180 436 | } 437 | ] 438 | } 439 | ``` 440 | 441 | ## 3. 工程文件相关 442 | 443 | ### 3.1 上传工程文件 444 | > 注意:上传工程文件会导致文件监控被清除 445 | * 请求地址 446 | 447 | | 方法 | URL | 448 | |--------|------------------| 449 | | `PUT` | `/file/upload` | 450 | * 请求参数 451 | 452 | | 参数 | 类型 | 说明 | 453 | |------|------|--------------| 454 | | file | 文件 | axf或者elf文件 | 455 | * 响应结果 456 | 457 | 无 458 | * 调用示例 459 | 460 | 请求示例: 461 | `PUT /file/upload` 462 | ``` 463 | file=@asuwave.elf 464 | ``` 465 | 响应示例: 466 | 无 467 | 468 | ### 3.2 获取监听的工程文件路径 469 | * 请求地址 470 | 471 | | 方法 | URL | 472 | |--------|------------------| 473 | | `GET` | `/file/path` | 474 | * 请求参数 475 | 476 | 无 477 | * 响应结果 478 | 479 | | 参数 | 类型 | 说明 | 480 | |------|--------|-------------------| 481 | | Path | string | axf或者elf文件路径 | 482 | * 调用示例 483 | 484 | 请求示例: 485 | `GET /file/path` 486 | ```json 487 | { 488 | "Path": "C:/user/scutrobotlab/robot.axf" 489 | } 490 | ``` 491 | 响应示例: 492 | 无 493 | 494 | ### 3.3 设置监听的工程文件路径 495 | * 请求地址 496 | 497 | | 方法 | URL | 498 | |---------|------------------| 499 | | `PUT` | `/file/path` | 500 | * 请求参数 501 | 502 | | 参数 | 类型 | 说明 | 503 | |------|--------|-------------------| 504 | | Path | string | axf或者elf文件路径 | 505 | * 响应结果 506 | 507 | 无 508 | * 调用示例 509 | 510 | 请求示例: 511 | `PUT /file/path` 512 | ```json 513 | { 514 | "Path": "C:/user/scutrobotlab/robot.axf" 515 | } 516 | ``` 517 | 响应示例: 518 | 无 519 | 520 | ### 3.4 清除监听的工程文件路径 521 | * 请求地址 522 | 523 | | 方法 | URL | 524 | |--------|------------------| 525 | | `DELETE` | `/file/path` | 526 | * 请求参数 527 | 528 | 无 529 | * 响应结果 530 | 531 | 无 532 | * 调用示例 533 | 534 | 请求示例: 535 | 无 536 | 响应示例: 537 | 无 538 | 539 | ## 4. 设置 540 | 541 | ### 4.1 查看设置 542 | * 请求地址 543 | 544 | | 方法 | URL | 545 | |-------|------------| 546 | | `GET` | `/option` | 547 | * 请求参数 548 | 549 | 无 550 | * 响应结果 551 | 552 | | 参数 | 类型 | 说明 | 553 | |-------|-------|-----------| 554 | | Save | int | 保存选项 | 555 | * 调用示例 556 | 557 | 请求示例: 558 | `GET /option` 559 | 响应示例: 560 | ```json 561 | { 562 | "Save": 7 563 | } 564 | ``` 565 | 566 | ### 4.2 修改设置 567 | * 请求地址 568 | 569 | | 方法 | URL | 570 | |-------|------------| 571 | | `PUT` | `/option` | 572 | * 请求参数 573 | 574 | | 参数 | 类型 | 说明 | 575 | |-------|-------|-----------| 576 | | Save | int | 保存选项 | 577 | * 响应结果 578 | 579 | | 参数 | 类型 | 说明 | 580 | |-------|-------|-----------| 581 | | Save | int | 保存选项 | 582 | * 调用示例 583 | 584 | 请求示例: 585 | `PUT /option` 586 | ```json 587 | { 588 | "Save": 6 589 | } 590 | ``` 591 | 响应示例: 592 | ```json 593 | { 594 | "Save": 6 595 | } 596 | ``` 597 | -------------------------------------------------------------------------------- /docs/protocol_serial.md: -------------------------------------------------------------------------------- 1 | # 通信协议 2 | 3 | ## 示意图 4 | 5 | ``` 6 | +-------------+ Serial +-------------+ TCP +-------------+ 7 | | MCU |------->| Server |------->| Client | 8 | | |<-------| Backend |<-------| Frontend | 9 | +-------------+ +-------------+ +-------------+ 10 | ``` 11 | 12 | ## 串口 13 | 14 | ### 数据包结构 15 | 16 | #### 单片机接收 17 | | 字节下标号 | 描述 | 18 | | -------- | ---- | 19 | | 0 | [单片机代号](#单片机代号) | 20 | | 1 | [请求代号](#请求代号) | 21 | | 2 | 数据长度 | 22 | | 3-6 | 单片机地址 | 23 | | 7-14 | 数据 | 24 | | 15 | 尾部固定为0x0a | 25 | 26 | #### 单片机发送 27 | | 字节下标号 | 描述 | 28 | | -------- | ---- | 29 | | 0 | [单片机代号](#单片机代号) | 30 | | 1 | [响应或错误代号](#响应或错误代号) | 31 | | 2 | 数据长度 | 32 | | 3-6 | 单片机地址 | 33 | | 7-14 | 数据 | 34 | | 15-18 | 时间戳 | 35 | | 19 | 尾部固定为0x0a | 36 | 37 | ### 单片机代号 38 | 为以后扩展保留,目前只取值为`0x01` 39 | 40 | ### 请求代号 41 | | 值 | 描述 | 42 | | ---- | ---- | 43 | | 0x01 | 订阅位于给定单片机地址和数据长度的值,此后单片机会一直发送位于该地址的变量值 | 44 | | 0x03 | 取消订阅位于给定单片机地址和数据长度的值 | 45 | | 0x05 | 读取位于给定单片机地址和数据长度的值 | 46 | | 0x07 | 修改位于给定单片机地址和数据长度的值 | 47 | 48 | ### 响应或错误代号 49 | | 值 | 描述 | 50 | | ---- | ---- | 51 | | 0x02 | 订阅的正常返回 | 52 | | 0x04 | 取消订阅的正常返回 | 53 | | 0x06 | 读取的正常返回 | 54 | | 0x08 | 修改的正常返回 | 55 | | 0xf9 | 取消订阅未订阅的地址 | 56 | | 0xfa | 订阅变量已达上限 | 57 | | 0xfb | 不支持的数据长度 | 58 | | 0xfc | 不支持的地址 | 59 | | 0xfd | 不支持的请求代号 | 60 | | 0xfe | 不支持的单片机代号 | 61 | -------------------------------------------------------------------------------- /docs/protocol_tcp.md: -------------------------------------------------------------------------------- 1 | # 通信协议 2 | 3 | ## 示意图 4 | 5 | ``` 6 | +-------------+ Serial +-------------+ TCP +-------------+ 7 | | MCU |------->| Server |------->| Client | 8 | | |<-------| Backend |<-------| Frontend | 9 | +-------------+ +-------------+ +-------------+ 10 | ``` 11 | 12 | 1. 如无特殊说明,所有请求和响应参数均为JSON格式。 13 | 2. 请求成功则状态码为2XX。 14 | 3. 请求失败状态码为4XX或5XX,具体错误信息在返回JSON的Error中。 15 | 16 | ## 1. 变量 17 | 18 | ### 1.1 查看订阅变量的值 19 | * 请求地址 20 | 21 | | URL | 22 | |-------| 23 | | `/ws` | 24 | * 响应结果 25 | 26 | | 参数 | 类型 | 说明 | 27 | |-------------------|--------------|---------| 28 | | Variables | array struct | 变量列表 | 29 | | Variables[].Board | int | 板子代号 | 30 | | Variables[].Name | string | 变量名 | 31 | | Variables[].Data | float | 变量值 | 32 | | Variables[].Tick | int | 时间戳 | 33 | * 调用示例 34 | 35 | 响应示例: 36 | ``` 37 | { 38 | "Variables":[ 39 | { 40 | "Board":1, 41 | "Name":"traceme", 42 | "Data":2.5, 43 | "Tick":10 44 | }, 45 | { 46 | "Board":1, 47 | "Name":"count", 48 | "Data":1, 49 | "Tick":10 50 | } 51 | ] 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/scutrobotlab/asuwave 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gorilla/websocket v1.5.0 7 | go.bug.st/serial v1.3.5 8 | ) 9 | 10 | require ( 11 | github.com/creack/goselect v0.1.2 // indirect 12 | github.com/fsnotify/fsnotify v1.5.4 13 | github.com/golang/glog v1.0.0 14 | golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= 2 | github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 5 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 6 | github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= 7 | github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= 8 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 9 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 12 | go.bug.st/serial v1.3.5 h1:k50SqGZCnHZ2MiBQgzccXWG+kd/XpOs1jUljpDDKzaE= 13 | go.bug.st/serial v1.3.5/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304= 14 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 15 | golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY= 16 | golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 18 | -------------------------------------------------------------------------------- /internal/helper/download.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/golang/glog" 12 | ) 13 | 14 | func PrintDownloadPercent(done chan int64, path string, total int64) { 15 | var stop bool = false 16 | for { 17 | select { 18 | case <-done: 19 | stop = true 20 | default: 21 | file, err := os.Open(path) 22 | if err != nil { 23 | glog.Fatalln(err.Error()) 24 | } 25 | fi, err := file.Stat() 26 | if err != nil { 27 | glog.Fatalln(err.Error()) 28 | } 29 | size := fi.Size() 30 | if size == 0 { 31 | size = 1 32 | } 33 | var percent float64 = float64(size) / float64(total) * 100 34 | fmt.Printf("%.0f", percent) 35 | fmt.Println("%") 36 | } 37 | if stop { 38 | break 39 | } 40 | time.Sleep(time.Second) 41 | } 42 | } 43 | 44 | func DownloadFile(url, filename string) error { 45 | fmt.Println("downloading...") 46 | 47 | file, err := os.Create(filename) 48 | if err != nil { 49 | panic(err) 50 | } 51 | defer file.Close() 52 | 53 | headResp, err := http.Head(url) 54 | if err != nil { 55 | panic(err) 56 | } 57 | defer headResp.Body.Close() 58 | 59 | size, err := strconv.Atoi(headResp.Header.Get("Content-Length")) 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | done := make(chan int64) 65 | go PrintDownloadPercent(done, filename, int64(size)) 66 | 67 | resp, err := http.Get(url) 68 | if err != nil { 69 | return fmt.Errorf("network error: %s", err.Error()) 70 | } 71 | defer resp.Body.Close() 72 | if resp.StatusCode != http.StatusOK { 73 | return fmt.Errorf("bad status: %s", resp.Status) 74 | } 75 | 76 | n, err := io.Copy(file, resp.Body) 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | done <- n 82 | 83 | fmt.Println("download complete: " + filename) 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /internal/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "os/exec" 10 | "path" 11 | "runtime" 12 | "strings" 13 | ) 14 | 15 | var ( 16 | Port int 17 | GitTag string 18 | GitHash string 19 | BuildTime string 20 | GoVersion string 21 | ) 22 | 23 | type githubRelease struct { 24 | TagName string `json:"tag_name"` 25 | Assets []struct { 26 | Name string `json:"name"` 27 | BrowserDownloadURL string `json:"browser_download_url"` 28 | } `json:"assets"` 29 | } 30 | 31 | func init() { 32 | if _, err := os.Stat(AppConfigDir()); os.IsNotExist(err) { 33 | err := os.MkdirAll(AppConfigDir(), 0755) 34 | if err != nil { 35 | panic(err) 36 | } 37 | } 38 | } 39 | 40 | func AppConfigDir() string { 41 | dir, err := os.UserConfigDir() 42 | if err != nil { 43 | dir = "./" 44 | } 45 | 46 | return path.Join(dir, "asuwave") 47 | } 48 | 49 | func GetVersion() string { 50 | return fmt.Sprintf("asuwave %s\nbuild time %s\n%s", GitHash, BuildTime, GoVersion) 51 | } 52 | 53 | func CheckUpdate(auto bool) { 54 | resp, err := http.Get("https://api.github.com/repos/scutrobotlab/asuwave/releases/latest") 55 | if err != nil { 56 | fmt.Println("network error: " + err.Error()) 57 | return 58 | } 59 | defer resp.Body.Close() 60 | body, _ := io.ReadAll(resp.Body) 61 | var gr githubRelease 62 | if err := json.Unmarshal([]byte(body), &gr); err != nil { 63 | return 64 | } 65 | if GitTag == gr.TagName { 66 | fmt.Println("already the latest version: " + GitTag) 67 | return 68 | } 69 | for _, asset := range gr.Assets { 70 | if strings.Contains(asset.Name, runtime.GOOS+"_"+runtime.GOARCH) { 71 | fmt.Println("current version is " + GitTag) 72 | fmt.Println("new version available: " + gr.TagName) 73 | fmt.Print("download now? (y/n) ") 74 | var a string 75 | if !auto { 76 | fmt.Scanln(&a) 77 | } 78 | if auto || a == "y" || a == "Y" || a == "yes" { 79 | if err := DownloadFile(asset.BrowserDownloadURL, asset.Name); err != nil { 80 | fmt.Println("download error: " + err.Error()) 81 | fmt.Println("trying hub.fastgit.org...") 82 | asset.BrowserDownloadURL = strings.Replace(asset.BrowserDownloadURL, "https://github.com", "https://hub.fastgit.org", 1) 83 | DownloadFile(asset.BrowserDownloadURL, asset.Name) 84 | } 85 | } 86 | return 87 | } 88 | } 89 | fmt.Printf("don't know your platform: %s, %s", runtime.GOOS, runtime.GOARCH) 90 | } 91 | 92 | func StartBrowser(url string) { 93 | var commands = map[string]string{ 94 | "windows": "explorer.exe", 95 | "darwin": "open", 96 | "linux": "xdg-open", 97 | } 98 | run, ok := commands[runtime.GOOS] 99 | if !ok { 100 | fmt.Printf("don't know how to open things on %s platform", runtime.GOOS) 101 | } else { 102 | go func() { 103 | fmt.Println("Your browser will start") 104 | exec.Command(run, url).Start() 105 | }() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /internal/option/option.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "path" 7 | "strconv" 8 | 9 | "github.com/golang/glog" 10 | "github.com/scutrobotlab/asuwave/internal/helper" 11 | "github.com/scutrobotlab/asuwave/internal/variable" 12 | "github.com/scutrobotlab/asuwave/pkg/elffile" 13 | "github.com/scutrobotlab/asuwave/pkg/jsonfile" 14 | ) 15 | 16 | var ( 17 | logLevel int 18 | saveFilePath bool 19 | ) 20 | 21 | var ( 22 | optionPath = path.Join(helper.AppConfigDir(), "option.json") 23 | fileWatchPath = path.Join(helper.AppConfigDir(), "FileWatch.json") 24 | ) 25 | 26 | type OptT struct { 27 | LogLevel int 28 | SaveFilePath bool 29 | SaveVarList bool 30 | UpdateByProj bool 31 | } 32 | 33 | func Get() OptT { 34 | return OptT{ 35 | LogLevel: logLevel, 36 | SaveFilePath: saveFilePath, 37 | SaveVarList: variable.GetOptSaveVarList(), 38 | UpdateByProj: variable.GetOptUpdateByProj(), 39 | } 40 | } 41 | 42 | func Load() { 43 | var opt OptT 44 | jsonfile.Load(optionPath, &opt) 45 | variable.SetOptSaveVarList(opt.SaveVarList) 46 | variable.SetOptUpdateByProj(opt.UpdateByProj) 47 | 48 | var watchList []string 49 | jsonfile.Load(fileWatchPath, &watchList) 50 | for _, w := range watchList { 51 | elffile.ChFileWatch <- w 52 | } 53 | 54 | jsonfile.Save(fileWatchPath, elffile.GetWatchList()) 55 | jsonfile.Save(optionPath, opt) 56 | } 57 | 58 | func SetLogLevel(v int) { 59 | if logLevel == v { 60 | glog.V(1).Infof("LogLevel has set to %d, skip\n", v) 61 | return 62 | } 63 | glog.V(1).Infof("Set LogLevel to %d\n", v) 64 | logLevel = v 65 | if err := flag.Set("v", strconv.Itoa(v)); err != nil { 66 | glog.Errorln(err.Error()) 67 | } 68 | jsonfile.Save(optionPath, Get()) 69 | } 70 | 71 | func SetSaveFilePath(v bool) { 72 | if saveFilePath == v { 73 | glog.V(1).Infof("SaveFilePath has set to %t, skip\n", v) 74 | return 75 | } 76 | glog.V(1).Infof("Set SaveFilePath to %t\n", v) 77 | if v { 78 | jsonfile.Save(fileWatchPath, elffile.GetWatchList()) 79 | } else { 80 | os.Remove(fileWatchPath) 81 | } 82 | saveFilePath = v 83 | jsonfile.Save(optionPath, Get()) 84 | } 85 | 86 | func SetSaveVarList(v bool) { 87 | variable.SetOptSaveVarList(v) 88 | jsonfile.Save(optionPath, Get()) 89 | } 90 | 91 | func SetUpdateByProj(v bool) { 92 | variable.SetOptUpdateByProj(v) 93 | jsonfile.Save(optionPath, Get()) 94 | } 95 | -------------------------------------------------------------------------------- /internal/option/option_test.go: -------------------------------------------------------------------------------- 1 | package option_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/scutrobotlab/asuwave/internal/option" 7 | ) 8 | 9 | func FuzzOption(f *testing.F) { 10 | f.Fuzz(func(t *testing.T, logLevel int, saveFilePath bool, saveVarList bool, updateByProj bool) { 11 | 12 | option.SetLogLevel(logLevel) 13 | option.SetSaveFilePath(saveFilePath) 14 | option.SetSaveVarList(saveVarList) 15 | option.SetUpdateByProj(updateByProj) 16 | 17 | got := option.Get() 18 | 19 | assertEQ(t, got.LogLevel, logLevel) 20 | assertEQ(t, got.SaveFilePath, saveFilePath) 21 | assertEQ(t, got.SaveVarList, saveVarList) 22 | assertEQ(t, got.UpdateByProj, updateByProj) 23 | }) 24 | } 25 | 26 | func assertEQ[V int | bool](t *testing.T, a V, b V) { 27 | if a != b { 28 | t.Errorf("%v != %v", a, b) 29 | } else { 30 | t.Logf("%v == %v", a, b) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/serial/serial.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strings" 7 | "time" 8 | 9 | "go.bug.st/serial" 10 | 11 | "github.com/golang/glog" 12 | "github.com/scutrobotlab/asuwave/internal/variable" 13 | ) 14 | 15 | type T struct { 16 | Name string 17 | Mode serial.Mode 18 | Port serial.Port 19 | } 20 | 21 | var SerialCur = T{ 22 | Name: "", 23 | Mode: serial.Mode{ 24 | BaudRate: 115200, 25 | Parity: serial.NoParity, 26 | DataBits: 8, 27 | StopBits: serial.OneStopBit, 28 | }, 29 | Port: nil, 30 | } 31 | 32 | var Chch = make(chan string) // 新图表Json 33 | 34 | var chOp = make(chan bool) // 敞开心扉 35 | var chEd = make(chan bool) // 沉默不语 36 | var chRx = make(chan []byte, 10) // 来信收讫 37 | var chTx = make(chan []byte, 10) // 去信已至 38 | 39 | const testPortName = "Test port" 40 | 41 | // Find ports 42 | func Find() []string { 43 | var ports []string 44 | ports = append(ports, testPortName) 45 | 46 | tmp, err := serial.GetPortsList() 47 | if err != nil { 48 | glog.Errorln("Serial ports errors: ", err.Error()) 49 | } 50 | if len(tmp) == 0 { 51 | glog.Infoln("No serial ports found!") 52 | } 53 | for _, port := range tmp { 54 | if strings.Contains(port, "USB") || strings.Contains(port, "ACM") || strings.Contains(port, "COM") || strings.Contains(port, "tty.usb") { 55 | ports = append(ports, port) 56 | } 57 | } 58 | return ports 59 | } 60 | 61 | // Open serial port 62 | func Open(name string, baud int) error { 63 | SerialCur.Name = name 64 | SerialCur.Mode.BaudRate = baud 65 | 66 | if name == testPortName { 67 | SerialCur.Port = newTestPort() 68 | chOp <- true 69 | return nil 70 | } 71 | 72 | var err error 73 | SerialCur.Port, err = serial.Open(SerialCur.Name, &SerialCur.Mode) 74 | if err != nil { 75 | SerialCur.Name = "" 76 | return err 77 | } 78 | glog.Infoln(SerialCur.Name, "Opened.") 79 | chOp <- true 80 | return nil 81 | } 82 | 83 | // Close serial port 84 | func Close() error { 85 | if SerialCur.Name == "" { 86 | return errors.New("serial port had closed") 87 | } 88 | 89 | err := SerialCur.Port.Close() 90 | if err != nil { 91 | return err 92 | } 93 | glog.Infoln(SerialCur.Name, "Closed.") 94 | SerialCur.Name = "" 95 | chEd <- true 96 | return nil 97 | } 98 | 99 | //Transmit data 100 | func Transmit(data []byte) error { 101 | glog.V(3).Infoln("serial port write: ", data) 102 | _, err := SerialCur.Port.Write(data) 103 | if err != nil { 104 | return err 105 | } 106 | return nil 107 | } 108 | 109 | //Receive data 110 | func Receive(buff []byte) ([]byte, error) { 111 | n, err := SerialCur.Port.Read(buff) 112 | glog.V(5).Infoln("serial port read: ", n) 113 | if err != nil { 114 | return nil, err 115 | } 116 | if n == 0 { 117 | return buff[0:0], nil 118 | } 119 | return buff[:n], nil 120 | } 121 | 122 | var adding = map[variable.CmdT]time.Time{} 123 | var deling = map[variable.CmdT]time.Time{} 124 | 125 | func SendWriteCmd(v variable.T) error { 126 | if SerialCur.Port == nil || SerialCur.Name == "" { 127 | return errors.New("no serial port") 128 | } 129 | 130 | glog.Infoln("Send write cmd", v) 131 | data := variable.MakeWriteCmd(v) 132 | chTx <- data 133 | return nil 134 | } 135 | 136 | func SendCmd(act variable.ActMode, v variable.CmdT) error { 137 | if SerialCur.Port == nil || SerialCur.Name == "" { 138 | return errors.New("no serial port") 139 | } 140 | 141 | if act == variable.Subscribe { 142 | if t, ok := adding[v]; ok { 143 | if time.Since(t) < time.Second { 144 | glog.V(2).Infoln("Has sent subscribe cmd recently", v) 145 | return nil 146 | } 147 | } 148 | adding[v] = time.Now() 149 | } else if act == variable.Unsubscribe { 150 | if t, ok := deling[v]; ok { 151 | if time.Since(t) < time.Second { 152 | glog.V(2).Infoln("Has sent unsubscribe cmd recently", v) 153 | return nil 154 | } 155 | } 156 | deling[v] = time.Now() 157 | } 158 | 159 | glog.Infoln("Send cmd", act, v) 160 | data := variable.MakeCmd(act, v) 161 | chTx <- data 162 | return nil 163 | } 164 | 165 | func GrReceive() { 166 | buff := make([]byte, 200) 167 | for { 168 | <-chOp 169 | glog.V(4).Infoln("chOp...") 170 | Loop: 171 | for { 172 | select { 173 | case <-chEd: 174 | glog.V(4).Infoln("GrReceive: got chEd...") 175 | break Loop 176 | default: 177 | glog.V(4).Infoln("GrReceive: default...") 178 | b, err := Receive(buff) 179 | if err != nil { 180 | glog.Errorln("GrReceive error:", err) 181 | } 182 | glog.V(4).Infoln("GrReceive b: ", b) 183 | chRx <- b 184 | glog.V(4).Infoln("GrReceive: send chRx...") 185 | time.Sleep(5 * time.Millisecond) 186 | } 187 | } 188 | time.Sleep(5 * time.Millisecond) 189 | } 190 | } 191 | 192 | func GrTransmit() { 193 | for { 194 | glog.V(4).Infoln("GrTransmit: ") 195 | b := <-chTx 196 | glog.V(4).Infoln("GrTransmit: got chTx...") 197 | err := Transmit(b) 198 | if err != nil { 199 | glog.Errorln("GrTransmit error: ", err) 200 | } 201 | time.Sleep(3 * time.Millisecond) 202 | } 203 | } 204 | 205 | func GrRxPrase() { 206 | var rxBuff []byte 207 | for { 208 | select { 209 | case rx := <-chRx: // 收到你的来信 210 | glog.V(4).Infoln("GrRxPrase: got chRx...") 211 | 212 | glog.V(4).Infoln("had buff: ", rxBuff) 213 | 214 | rxBuff = append(rxBuff, rx...) // 深藏我的心底 215 | 216 | glog.V(4).Infoln("got buff: ", rxBuff) 217 | 218 | // 解开长情的信笺 219 | // 残余的信亦不能忘却 220 | var vars []variable.CmdT 221 | vars, rxBuff = variable.Unpack(rxBuff) 222 | 223 | // 所有的酸甜苦辣都值得铭记 224 | glog.V(4).Infoln("left buff: ", rxBuff) 225 | glog.V(4).Infof("got vars: %v\n", vars) 226 | 227 | if len(vars) > 10 { 228 | glog.Fatalln("Too many vars") 229 | } 230 | 231 | // 拼凑出变量的清单 232 | chart, add, del := variable.Filt(vars) 233 | if len(chart) != 0 { 234 | b, _ := json.Marshal(chart) 235 | Chch <- string(b) 236 | } 237 | 238 | glog.V(3).Infoln("len(chart): ", len(chart)) 239 | if glog.V(2) && len(add) > 0 || len(del) > 0 { 240 | glog.Infof("add: %v, del: %v\n", add, del) 241 | } 242 | 243 | // 挂念的变量,还望顺问近祺 244 | go func() { 245 | for _, v := range add { 246 | err := SendCmd(variable.Subscribe, v) 247 | if err != nil { 248 | glog.Errorln("SendCmd error:", err) 249 | } 250 | } 251 | }() 252 | 253 | // 无缘的变量,就请随风逝去 254 | go func() { 255 | for _, v := range del { 256 | err := SendCmd(variable.Unsubscribe, v) 257 | if err != nil { 258 | glog.Errorln("SendCmd error:", err) 259 | } 260 | } 261 | }() 262 | 263 | case <-time.After(200 * time.Millisecond): // 200ms不见 264 | if SerialCur.Port == nil || SerialCur.Name == "" { 265 | break 266 | } 267 | glog.V(4).Infoln("GrRxPrase: time after 200ms...") 268 | // 甚是想念 269 | _, add, _ := variable.Filt([]variable.CmdT{}) 270 | glog.V(3).Infoln("add: ", add) 271 | for _, v := range add { 272 | err := SendCmd(variable.Subscribe, v) 273 | if err != nil { 274 | glog.Errorln("SendCmd error:", err) 275 | } 276 | } 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /internal/serial/testport.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | "time" 8 | 9 | "go.bug.st/serial" 10 | 11 | "github.com/golang/glog" 12 | "github.com/scutrobotlab/asuwave/internal/variable" 13 | "github.com/scutrobotlab/asuwave/pkg/slip" 14 | ) 15 | 16 | /** 17 | 0x20000000 静止,可写变量 18 | 0x30000000 动态变化,double 19 | 0x40000000 动态变化,float 20 | 0x50000000 动态变化,int8 21 | 0x60000000 动态变化,int16 22 | 0x70000000 动态变化,int32 23 | 0x80000000 动态变化,int64 24 | **/ 25 | var vartypeMap = map[uint32]string{ 26 | 3: "double", 27 | 4: "float", 28 | 5: "int8_t", 29 | 6: "int16_t", 30 | 7: "int32_t", 31 | 8: "int64_t", 32 | } 33 | 34 | var ( 35 | chAddr = make(chan bool, 10) // 修改通知 36 | addresses map[uint32]bool = map[uint32]bool{} // 观察的地址 37 | writeData map[uint32][]byte = map[uint32][]byte{} // 直接写入数据 38 | BoardSysTime time.Time = time.Now() // 虚拟电路板的系统时间 39 | ) 40 | 41 | type testPort struct{} 42 | 43 | func newTestPort() serial.Port { 44 | glog.Infoln("TestPort open at: ", BoardSysTime) 45 | return &testPort{} 46 | } 47 | 48 | func (tp *testPort) SetMode(mode *serial.Mode) error { return nil } 49 | 50 | func testValue(x float64, addr uint32) []byte { 51 | if data, ok := writeData[addr]; ok { 52 | return data 53 | } 54 | 55 | ratio := float64(addr&0xF) * 8.0 56 | phase := float64((addr >> 4) & 0xF) 57 | freq := (float64((addr>>8)&0xFF) - 0x80) / 64.0 58 | freq = math.Exp(freq) 59 | amplitude := float64((addr>>16)&0xFF) / 16.0 60 | amplitude = math.Exp(amplitude) 61 | waveform := (addr >> 24) & 0xF 62 | vartype := (addr >> 28) & 0xF 63 | currentPhase := x*freq + phase 64 | dist := currentPhase - math.Floor(currentPhase) 65 | 66 | glog.V(5).Info("ratio=", ratio) 67 | glog.V(5).Info("phase=", phase) 68 | glog.V(5).Info("freq=", freq) 69 | glog.V(5).Info("amplitude=", amplitude) 70 | glog.V(5).Info("currentPhase=", currentPhase) 71 | glog.V(5).Info("dist=", dist) 72 | glog.V(5).Infof("vartype=%d(%s)", vartype, vartypeMap[vartype]) 73 | 74 | var scale float64 75 | switch waveform { 76 | case 0: // square 77 | glog.V(5).Info("waveform=square") 78 | if dist < 0.5 { 79 | scale = 1.0 80 | } else { 81 | scale = -1.0 82 | } 83 | case 1: // triangle 84 | glog.V(5).Info("waveform=triangle") 85 | scale = 4 * (math.Abs(dist-0.5) - 0.25) 86 | case 2: // scan 87 | glog.V(5).Info("waveform=scan") 88 | scale = 2 * (dist - 0.5) 89 | case 3: //exsin 90 | glog.V(5).Info("waveform=exsin") 91 | scale = math.Exp(x/ratio) * math.Sin(currentPhase*2*math.Pi) 92 | case 4: //e-xsin 93 | glog.V(5).Info("waveform=e-xsin") 94 | scale = math.Exp(-x/ratio) * math.Sin(currentPhase*2*math.Pi) 95 | default: // sin 96 | glog.V(5).Info("waveform=sin") 97 | scale = math.Sin(currentPhase * 2 * math.Pi) 98 | } 99 | y := scale * amplitude 100 | 101 | if t, ok := vartypeMap[vartype]; ok { 102 | data := variable.SpecToBytes(t, y) 103 | return data 104 | } 105 | 106 | return make([]byte, 8) 107 | } 108 | 109 | func (tp *testPort) Read(p []byte) (n int, err error) { 110 | for len(addresses) == 0 { 111 | if _, ok := <-chAddr; !ok { 112 | return 0, nil 113 | } 114 | } 115 | data := make([]byte, 0, len(addresses)*40) 116 | 117 | i := 0 118 | for addr := range addresses { 119 | var pdu [20]byte 120 | pdu[0] = 1 // 单片机代号 board 121 | pdu[1] = 2 // 响应或错误代号 act (0x02 = 订阅的正常返回) 122 | pdu[2] = 8 // 数据长度 length 123 | copy(pdu[3:7], variable.AnyToBytes(addr)) // 单片机地址 124 | t := time.Since(BoardSysTime) 125 | x := t.Seconds() 126 | u := t.Milliseconds() 127 | y := testValue(x, addr) 128 | copy(pdu[7:15], y) // 数据 129 | copy(pdu[15:19], variable.AnyToBytes(uint32(u))) // 时间戳 130 | pdu[19] = '\n' // 尾部固定为0x0a 131 | i++ 132 | sdu := slip.Pack(pdu[:]) 133 | data = append(data, sdu...) 134 | } 135 | 136 | return copy(p, data), nil 137 | } 138 | 139 | func (tp *testPort) Write(p []byte) (n int, err error) { 140 | if p[len(p)-1] != '\n' { 141 | return 0, errors.New("invalid package") 142 | } 143 | board := p[0] 144 | glog.Infoln("Got write: board = ", board) 145 | act := variable.ActMode(p[1]) 146 | glog.Infoln("Got write: act = ", act) 147 | length := p[2] 148 | glog.Infoln("Got write: length = ", length) 149 | address := variable.BytesToUint32(p[3:7]) 150 | glog.Infoln("Got write: address = ", address) 151 | data := p[7:15] 152 | 153 | switch act { 154 | case variable.Subscribe: 155 | go time.AfterFunc(500*time.Millisecond, func() { 156 | addresses[address] = true 157 | chAddr <- true 158 | glog.Infof("Adding address: %08X\n", address) 159 | }) 160 | 161 | case variable.Unsubscribe: 162 | go time.AfterFunc(500*time.Millisecond, func() { 163 | delete(addresses, address) 164 | chAddr <- true 165 | glog.Infof("Deleting address: %08X\n", address) 166 | }) 167 | 168 | case variable.Write: 169 | go time.AfterFunc(500*time.Millisecond, func() { 170 | writeData[address] = data 171 | glog.Infof("Writing address: %08X = %v\n", address, data) 172 | }) 173 | 174 | default: 175 | return 0, errors.New(fmt.Sprint("invalid act: ", act)) 176 | } 177 | 178 | return 16, nil 179 | } 180 | 181 | func (tp *testPort) ResetInputBuffer() error { return nil } 182 | 183 | func (tp *testPort) ResetOutputBuffer() error { return nil } 184 | 185 | func (tp *testPort) SetDTR(dtr bool) error { return errors.New("not supported") } 186 | 187 | func (tp *testPort) SetRTS(rts bool) error { return errors.New("not supported") } 188 | 189 | func (tp *testPort) SetReadTimeout(timeout time.Duration) error { return nil } 190 | 191 | func (tp *testPort) GetModemStatusBits() (*serial.ModemStatusBits, error) { 192 | return nil, errors.New("not supported") 193 | } 194 | 195 | func (tp *testPort) Close() error { 196 | close(chAddr) 197 | return nil 198 | } 199 | -------------------------------------------------------------------------------- /internal/server/ctrler_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/http/httptest" 9 | "runtime" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | type casesT []struct { 15 | method string 16 | url string 17 | body interface{} 18 | wantCode int 19 | } 20 | 21 | const pass = "\u2713" 22 | const fail = "\u2717" 23 | 24 | func ctrlerTest(ctrler func(http.ResponseWriter, *http.Request), cases casesT, t *testing.T) { 25 | _, file, no, _ := runtime.Caller(1) 26 | fmt.Printf("From: %s:%d\n", file, no) 27 | for _, c := range cases { 28 | fmt.Printf("%s %s ", c.method, c.url) 29 | 30 | var reqBody io.Reader = nil 31 | if c.body != nil { 32 | b, _ := json.Marshal(c.body) 33 | reqBody = strings.NewReader(string(b)) 34 | } 35 | req := httptest.NewRequest(c.method, c.url, reqBody) 36 | w := httptest.NewRecorder() 37 | ctrler(w, req) 38 | 39 | resp := w.Result() 40 | body, _ := io.ReadAll(resp.Body) 41 | if resp.StatusCode != c.wantCode { 42 | fmt.Errorf(`%s 43 | have: %d 44 | want: %d 45 | `, 46 | fail, 47 | resp.StatusCode, 48 | c.wantCode) 49 | } 50 | 51 | fmt.Printf(`%s 52 | status: %d 53 | body: %s 54 | `, 55 | pass, 56 | c.wantCode, 57 | body) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /internal/server/filectl.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/scutrobotlab/asuwave/internal/variable" 10 | "github.com/scutrobotlab/asuwave/pkg/elffile" 11 | ) 12 | 13 | // 上传elf或axf文件 14 | func fileUploadCtrl(w http.ResponseWriter, r *http.Request) { 15 | defer r.Body.Close() 16 | w.Header().Set("Content-Type", "application/json") 17 | switch r.Method { 18 | case http.MethodPut: 19 | err := elffile.RemoveWathcer() 20 | if err != nil { 21 | w.WriteHeader(http.StatusInternalServerError) 22 | io.WriteString(w, errorJson(err.Error())) 23 | return 24 | } 25 | 26 | r.ParseMultipartForm(32 << 20) 27 | file, _, err := r.FormFile("file") 28 | if err != nil { 29 | w.WriteHeader(http.StatusInternalServerError) 30 | io.WriteString(w, errorJson(err.Error())) 31 | return 32 | } 33 | defer file.Close() 34 | 35 | tempFile, err := os.CreateTemp("", "elf") 36 | if err != nil { 37 | w.WriteHeader(http.StatusInternalServerError) 38 | io.WriteString(w, errorJson(err.Error())) 39 | return 40 | } 41 | defer os.Remove(tempFile.Name()) 42 | 43 | io.Copy(tempFile, file) 44 | 45 | f, err := elffile.Check(tempFile) 46 | if err != nil { 47 | w.WriteHeader(http.StatusInternalServerError) 48 | io.WriteString(w, errorJson(err.Error())) 49 | return 50 | } 51 | defer f.Close() 52 | 53 | projs, err := elffile.ReadVariable(f) 54 | if err != nil { 55 | w.WriteHeader(http.StatusInternalServerError) 56 | io.WriteString(w, errorJson(err.Error())) 57 | return 58 | } 59 | variable.SetAllProj(projs) 60 | 61 | w.WriteHeader(http.StatusNoContent) 62 | io.WriteString(w, "") 63 | default: 64 | w.WriteHeader(http.StatusMethodNotAllowed) 65 | io.WriteString(w, errorJson(http.StatusText(http.StatusMethodNotAllowed))) 66 | } 67 | } 68 | 69 | // 监控elf或axf文件 70 | func filePathCtrl(w http.ResponseWriter, r *http.Request) { 71 | defer r.Body.Close() 72 | w.Header().Set("Content-Type", "application/json") 73 | switch r.Method { 74 | case http.MethodGet: 75 | j := elffile.GetWatchList() 76 | b, _ := json.Marshal(j) 77 | io.WriteString(w, string(b)) 78 | 79 | case http.MethodPut: 80 | j := struct { 81 | Path string 82 | }{} 83 | data, _ := io.ReadAll(r.Body) 84 | err := json.Unmarshal(data, &j) 85 | if err != nil { 86 | w.WriteHeader(http.StatusBadRequest) 87 | io.WriteString(w, errorJson("Invaild json")) 88 | return 89 | } 90 | 91 | file, err := os.Open(j.Path) 92 | if err != nil { 93 | w.WriteHeader(http.StatusInternalServerError) 94 | io.WriteString(w, errorJson(err.Error())) 95 | return 96 | } 97 | 98 | f, err := elffile.Check(file) 99 | if err != nil { 100 | w.WriteHeader(http.StatusInternalServerError) 101 | io.WriteString(w, errorJson(err.Error())) 102 | return 103 | } 104 | defer f.Close() 105 | 106 | projs, err := elffile.ReadVariable(f) 107 | if err != nil { 108 | w.WriteHeader(http.StatusInternalServerError) 109 | io.WriteString(w, errorJson(err.Error())) 110 | return 111 | } 112 | variable.SetAllProj(projs) 113 | 114 | elffile.ChFileWatch <- j.Path 115 | if err != nil { 116 | w.WriteHeader(http.StatusInternalServerError) 117 | io.WriteString(w, errorJson(err.Error())) 118 | return 119 | } 120 | 121 | w.WriteHeader(http.StatusNoContent) 122 | io.WriteString(w, "") 123 | 124 | case http.MethodDelete: 125 | err := elffile.RemoveWathcer() 126 | if err != nil { 127 | w.WriteHeader(http.StatusInternalServerError) 128 | io.WriteString(w, errorJson(err.Error())) 129 | return 130 | } 131 | 132 | w.WriteHeader(http.StatusNoContent) 133 | io.WriteString(w, "") 134 | 135 | default: 136 | w.WriteHeader(http.StatusMethodNotAllowed) 137 | io.WriteString(w, errorJson(http.StatusText(http.StatusMethodNotAllowed))) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /internal/server/optionctrl.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/scutrobotlab/asuwave/internal/option" 10 | ) 11 | 12 | func optionCtrl(w http.ResponseWriter, r *http.Request) { 13 | defer r.Body.Close() 14 | 15 | switch r.Method { 16 | case http.MethodGet: 17 | b, _ := json.Marshal(option.Get()) 18 | io.WriteString(w, string(b)) 19 | 20 | case http.MethodPut: 21 | j := struct { 22 | Key string 23 | Value *json.RawMessage 24 | }{} 25 | postData, _ := io.ReadAll(r.Body) 26 | if err := json.Unmarshal(postData, &j); err != nil { 27 | w.WriteHeader(http.StatusBadRequest) 28 | io.WriteString(w, errorJson("Invaild json")) 29 | return 30 | } 31 | value := string(*j.Value) 32 | switch j.Key { 33 | case "LogLevel": 34 | if v, err := strconv.Atoi(value); err == nil && v >= 0 && v <= 5 { 35 | option.SetLogLevel(v) 36 | } else { 37 | w.WriteHeader(http.StatusBadRequest) 38 | io.WriteString(w, errorJson("Invaild value")) 39 | return 40 | } 41 | case "SaveVarList": 42 | if v, err := strconv.ParseBool(value); err == nil { 43 | option.SetSaveVarList(v) 44 | } else { 45 | w.WriteHeader(http.StatusBadRequest) 46 | io.WriteString(w, errorJson("Invaild value")) 47 | return 48 | } 49 | case "SaveFilePath": 50 | if v, err := strconv.ParseBool(value); err == nil { 51 | option.SetSaveFilePath(v) 52 | } else { 53 | w.WriteHeader(http.StatusBadRequest) 54 | io.WriteString(w, errorJson("Invaild value")) 55 | return 56 | } 57 | case "UpdateByProj": 58 | if v, err := strconv.ParseBool(value); err == nil { 59 | option.SetUpdateByProj(v) 60 | } else { 61 | w.WriteHeader(http.StatusBadRequest) 62 | io.WriteString(w, errorJson("Invaild value")) 63 | return 64 | } 65 | default: 66 | w.WriteHeader(http.StatusBadRequest) 67 | io.WriteString(w, errorJson("Unfound key: "+j.Key)) 68 | return 69 | } 70 | w.WriteHeader(http.StatusNoContent) 71 | io.WriteString(w, "") 72 | 73 | default: 74 | w.WriteHeader(http.StatusMethodNotAllowed) 75 | io.WriteString(w, errorJson(http.StatusText(http.StatusMethodNotAllowed))) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /internal/server/optionctrl_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | ) 7 | 8 | func TestOptionCtrl(t *testing.T) { 9 | cases := casesT{ 10 | { 11 | http.MethodGet, 12 | "/options", 13 | nil, 14 | http.StatusOK, 15 | }, 16 | { 17 | http.MethodPost, 18 | "/options", 19 | nil, 20 | http.StatusMethodNotAllowed, 21 | }, 22 | { 23 | http.MethodPut, 24 | "/options", 25 | struct { 26 | Key string 27 | Value int 28 | }{ 29 | Key: "LogLevel", 30 | Value: 3, 31 | }, 32 | http.StatusOK, 33 | }, 34 | { 35 | http.MethodDelete, 36 | "/options", 37 | nil, 38 | http.StatusMethodNotAllowed, 39 | }, 40 | { 41 | http.MethodGet, 42 | "/options", 43 | nil, 44 | http.StatusOK, 45 | }, 46 | } 47 | ctrlerTest(optionCtrl, cases, t) 48 | } 49 | -------------------------------------------------------------------------------- /internal/server/serialctrl.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | 8 | "github.com/scutrobotlab/asuwave/internal/serial" 9 | ) 10 | 11 | type SerialSetting struct { 12 | Serial string 13 | Baud int 14 | } 15 | 16 | func serialCtrl(w http.ResponseWriter, r *http.Request) { 17 | defer r.Body.Close() 18 | w.Header().Set("Content-Type", "application/json") 19 | 20 | switch r.Method { 21 | case http.MethodGet: 22 | j := struct{ Serials []string }{Serials: serial.Find()} 23 | b, _ := json.Marshal(j) 24 | io.WriteString(w, string(b)) 25 | 26 | default: 27 | w.WriteHeader(http.StatusMethodNotAllowed) 28 | io.WriteString(w, errorJson(http.StatusText(http.StatusMethodNotAllowed))) 29 | } 30 | } 31 | 32 | func serialCurCtrl(w http.ResponseWriter, r *http.Request) { 33 | defer r.Body.Close() 34 | w.Header().Set("Content-Type", "application/json") 35 | var err error 36 | 37 | switch r.Method { 38 | case http.MethodGet: 39 | j := SerialSetting{ 40 | Serial: serial.SerialCur.Name, 41 | Baud: serial.SerialCur.Mode.BaudRate, 42 | } 43 | b, _ := json.Marshal(j) 44 | io.WriteString(w, string(b)) 45 | 46 | case http.MethodPost: 47 | j := SerialSetting{} 48 | postData, _ := io.ReadAll(r.Body) 49 | err = json.Unmarshal(postData, &j) 50 | if err != nil { 51 | w.WriteHeader(http.StatusBadRequest) 52 | io.WriteString(w, errorJson("Invaild json")) 53 | return 54 | } 55 | 56 | err = serial.Open(j.Serial, j.Baud) 57 | if err != nil { 58 | w.WriteHeader(http.StatusInternalServerError) 59 | io.WriteString(w, errorJson(err.Error())) 60 | return 61 | } 62 | io.WriteString(w, string(postData)) 63 | 64 | case http.MethodDelete: 65 | err = serial.Close() 66 | if err != nil { 67 | w.WriteHeader(http.StatusInternalServerError) 68 | io.WriteString(w, errorJson(err.Error())) 69 | return 70 | } 71 | w.WriteHeader(http.StatusNoContent) 72 | io.WriteString(w, "") 73 | 74 | default: 75 | w.WriteHeader(http.StatusMethodNotAllowed) 76 | io.WriteString(w, errorJson(http.StatusText(http.StatusMethodNotAllowed))) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /internal/server/serialctrl_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/scutrobotlab/asuwave/internal/serial" 8 | ) 9 | 10 | func TestSerialCtrl(t *testing.T) { 11 | cases := casesT{ 12 | { 13 | http.MethodGet, 14 | "/serials", 15 | nil, 16 | http.StatusOK, 17 | }, 18 | { 19 | http.MethodPost, 20 | "/serials", 21 | nil, 22 | http.StatusMethodNotAllowed, 23 | }, 24 | { 25 | http.MethodPut, 26 | "/serials", 27 | nil, 28 | http.StatusMethodNotAllowed, 29 | }, 30 | { 31 | http.MethodDelete, 32 | "/serials", 33 | nil, 34 | http.StatusMethodNotAllowed, 35 | }, 36 | } 37 | ctrlerTest(serialCtrl, cases, t) 38 | } 39 | 40 | func TestSerialCurCtrl(t *testing.T) { 41 | go serial.GrReceive() 42 | go serial.GrTransmit() 43 | go serial.GrRxPrase() 44 | cases := casesT{ 45 | { 46 | http.MethodGet, 47 | "/serial_cur", 48 | nil, 49 | http.StatusOK, 50 | }, 51 | { 52 | http.MethodPost, 53 | "/serial_cur", 54 | struct{ Serial string }{Serial: "Test port"}, 55 | http.StatusOK, 56 | }, 57 | { 58 | http.MethodGet, 59 | "/serial_cur", 60 | nil, 61 | http.StatusOK, 62 | }, 63 | { 64 | http.MethodPut, 65 | "/serial_cur", 66 | struct{ Serial string }{Serial: "Test port"}, 67 | http.StatusMethodNotAllowed, 68 | }, 69 | { 70 | http.MethodDelete, 71 | "/serial_cur", 72 | nil, 73 | http.StatusNoContent, 74 | }, 75 | } 76 | ctrlerTest(serialCurCtrl, cases, t) 77 | } 78 | -------------------------------------------------------------------------------- /internal/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/fs" 7 | "mime" 8 | "net" 9 | "net/http" 10 | "strconv" 11 | 12 | "github.com/golang/glog" 13 | "github.com/scutrobotlab/asuwave/internal/helper" 14 | "github.com/scutrobotlab/asuwave/internal/variable" 15 | ) 16 | 17 | // Start server 18 | func Start(fsys *fs.FS) { 19 | port := ":" + strconv.Itoa(helper.Port) 20 | glog.Infoln("Listen on " + port) 21 | 22 | fmt.Println("asuwave running at:") 23 | fmt.Println("- Local: http://localhost" + port + "/") 24 | ips := getLocalIP() 25 | for _, ip := range ips { 26 | fmt.Println("- Network: http://" + ip + port + "/") 27 | } 28 | fmt.Println("Don't close this before you have done") 29 | 30 | variableToReadCtrl := makeVariableCtrl(variable.RD) 31 | variableToWriteCtrl := makeVariableCtrl(variable.WR) 32 | 33 | mime.AddExtensionType(".js", "application/javascript") 34 | http.Handle("/", http.FileServer(http.FS(*fsys))) 35 | 36 | http.Handle("/serial", logs(serialCtrl)) 37 | http.Handle("/serial_cur", logs(serialCurCtrl)) 38 | http.Handle("/variable_read", logs(variableToReadCtrl)) 39 | http.Handle("/variable_write", logs(variableToWriteCtrl)) 40 | http.Handle("/variable_proj", logs(variableToProjCtrl)) 41 | http.Handle("/variable_type", logs(variableTypeCtrl)) 42 | http.Handle("/file/upload", logs(fileUploadCtrl)) 43 | http.Handle("/file/path", logs(filePathCtrl)) 44 | http.Handle("/option", logs(optionCtrl)) 45 | http.Handle("/dataws", logs(dataWebsocketCtrl)) 46 | http.Handle("/filews", logs(fileWebsocketCtrl)) 47 | glog.Fatalln(http.ListenAndServe(port, nil)) 48 | } 49 | 50 | func logs(f func(http.ResponseWriter, *http.Request)) http.Handler { 51 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 52 | glog.Infoln(r.RemoteAddr, r.Method, r.URL) 53 | http.HandlerFunc(f).ServeHTTP(w, r) 54 | }) 55 | } 56 | 57 | func errorJson(s string) string { 58 | j := struct{ Error string }{s} 59 | b, _ := json.Marshal(j) 60 | return string(b) 61 | } 62 | 63 | func getLocalIP() []string { 64 | var ips []string 65 | addrs, err := net.InterfaceAddrs() 66 | if err != nil { 67 | return ips 68 | } 69 | 70 | for _, addr := range addrs { 71 | if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil { 72 | ips = append(ips, ipnet.IP.String()) 73 | } 74 | } 75 | 76 | return ips 77 | } 78 | -------------------------------------------------------------------------------- /internal/server/variablectrl.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | "sort" 8 | 9 | "github.com/scutrobotlab/asuwave/internal/serial" 10 | "github.com/scutrobotlab/asuwave/internal/variable" 11 | ) 12 | 13 | // vList 要控制的参数列表; 14 | // isVToRead 为true代表只读变量,为false代表可写变量 15 | func makeVariableCtrl(m variable.Mod) func(w http.ResponseWriter, r *http.Request) { 16 | return func(w http.ResponseWriter, r *http.Request) { 17 | defer r.Body.Close() 18 | w.Header().Set("Content-Type", "application/json") 19 | var err error 20 | switch r.Method { 21 | // 获取变量列表 22 | case http.MethodGet: 23 | b, _ := variable.GetAll(m) 24 | io.WriteString(w, string(b)) 25 | // 新增变量 26 | case http.MethodPost: 27 | var newVariable variable.T 28 | postData, _ := io.ReadAll(r.Body) 29 | err = json.Unmarshal(postData, &newVariable) 30 | if err != nil { 31 | w.WriteHeader(http.StatusBadRequest) 32 | io.WriteString(w, errorJson("Invaild json")) 33 | return 34 | } 35 | if newVariable.Addr < 0x20000000 || newVariable.Addr >= 0x80000000 { 36 | w.WriteHeader(http.StatusBadRequest) 37 | io.WriteString(w, errorJson("Address out of range")) 38 | return 39 | } 40 | if _, ok := variable.Get(m, newVariable.Addr); ok { 41 | w.WriteHeader(http.StatusBadRequest) 42 | io.WriteString(w, errorJson("Address already used")) 43 | return 44 | } 45 | variable.Set(m, newVariable.Addr, newVariable) 46 | w.WriteHeader(http.StatusNoContent) 47 | io.WriteString(w, "") 48 | // 为变量赋值 49 | case http.MethodPut: 50 | if m == variable.RD { 51 | w.WriteHeader(http.StatusMethodNotAllowed) 52 | io.WriteString(w, errorJson(http.StatusText(http.StatusMethodNotAllowed))) 53 | return 54 | } 55 | var modVariable variable.T 56 | postData, _ := io.ReadAll(r.Body) 57 | err = json.Unmarshal(postData, &modVariable) 58 | if err != nil { 59 | w.WriteHeader(http.StatusBadRequest) 60 | io.WriteString(w, errorJson("Invaild json")) 61 | return 62 | } 63 | if serial.SerialCur.Name == "" { 64 | w.WriteHeader(http.StatusInternalServerError) 65 | io.WriteString(w, "Not allow when serial port closed.") 66 | return 67 | } 68 | err = serial.SendWriteCmd(modVariable) 69 | if err != nil { 70 | w.WriteHeader(http.StatusInternalServerError) 71 | io.WriteString(w, errorJson(err.Error())) 72 | return 73 | } 74 | w.WriteHeader(http.StatusNoContent) 75 | io.WriteString(w, "") 76 | // 删除变量 77 | case http.MethodDelete: 78 | var oldVariable variable.T 79 | postData, _ := io.ReadAll(r.Body) 80 | err = json.Unmarshal(postData, &oldVariable) 81 | if err != nil { 82 | w.WriteHeader(http.StatusBadRequest) 83 | io.WriteString(w, errorJson("Invaild json")) 84 | return 85 | } 86 | 87 | // 我认为不必再检查是否存在这个变量 88 | // if _, ok := vList.Variables[oldVariable.Addr]; !ok { 89 | // w.WriteHeader(http.StatusBadRequest) 90 | // io.WriteString(w, errorJson("No such address")) 91 | // } 92 | 93 | // 从 vList.Variables 中删除地址为 oldVariable.Addr 的变量 94 | variable.Delete(m, oldVariable.Addr) 95 | w.WriteHeader(http.StatusNoContent) 96 | io.WriteString(w, "") 97 | return 98 | 99 | default: 100 | w.WriteHeader(http.StatusMethodNotAllowed) 101 | io.WriteString(w, errorJson(http.StatusText(http.StatusMethodNotAllowed))) 102 | } 103 | } 104 | } 105 | 106 | // 工程变量 107 | func variableToProjCtrl(w http.ResponseWriter, r *http.Request) { 108 | defer r.Body.Close() 109 | w.Header().Set("Content-Type", "application/json") 110 | switch r.Method { 111 | case http.MethodGet: 112 | b, _ := variable.GetAllProj() 113 | io.WriteString(w, string(b)) 114 | case http.MethodDelete: 115 | variable.SetAllProj(variable.Projs{}) 116 | 117 | w.WriteHeader(http.StatusNoContent) 118 | io.WriteString(w, "") 119 | default: 120 | w.WriteHeader(http.StatusMethodNotAllowed) 121 | io.WriteString(w, errorJson(http.StatusText(http.StatusMethodNotAllowed))) 122 | } 123 | } 124 | 125 | func variableTypeCtrl(w http.ResponseWriter, r *http.Request) { 126 | defer r.Body.Close() 127 | w.Header().Set("Content-Type", "application/json") 128 | 129 | switch r.Method { 130 | case http.MethodGet: 131 | var types struct{ Types []string } 132 | for k := range variable.TypeLen { 133 | types.Types = append(types.Types, k) 134 | } 135 | sort.Strings(types.Types) 136 | b, _ := json.Marshal(types) 137 | io.WriteString(w, string(b)) 138 | 139 | default: 140 | w.WriteHeader(http.StatusMethodNotAllowed) 141 | io.WriteString(w, errorJson(http.StatusText(http.StatusMethodNotAllowed))) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /internal/server/variablectrl_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/scutrobotlab/asuwave/internal/serial" 8 | "github.com/scutrobotlab/asuwave/internal/variable" 9 | ) 10 | 11 | func TestVariableToReadCtrl(t *testing.T) { 12 | cases := casesT{ 13 | { 14 | http.MethodGet, 15 | "/variable_read", 16 | nil, 17 | http.StatusOK, 18 | }, 19 | { 20 | http.MethodPost, 21 | "/variable_read", 22 | struct { 23 | Board uint8 24 | Name string 25 | Type string 26 | Addr uint32 27 | }{ 28 | Board: 1, 29 | Name: "a", 30 | Type: "int", 31 | Addr: 0x20123456, 32 | }, 33 | http.StatusNoContent, 34 | }, 35 | { 36 | http.MethodGet, 37 | "/variable_read", 38 | nil, 39 | http.StatusOK, 40 | }, 41 | { 42 | http.MethodPut, 43 | "/variable_read", 44 | nil, 45 | http.StatusMethodNotAllowed, 46 | }, 47 | { 48 | http.MethodDelete, 49 | "/variable_read", 50 | struct { 51 | Board uint8 52 | Name string 53 | Type string 54 | Addr uint32 55 | }{ 56 | Board: 1, 57 | Name: "a", 58 | Type: "int", 59 | Addr: 0x20123456, 60 | }, 61 | http.StatusNoContent, 62 | }, 63 | } 64 | 65 | variableToReadCtrl := makeVariableCtrl(variable.RD) 66 | 67 | ctrlerTest(variableToReadCtrl, cases, t) 68 | } 69 | 70 | func TestVariableToWriteCtrl(t *testing.T) { 71 | serial.Open("Test port", 115200) 72 | cases := casesT{ 73 | { 74 | http.MethodGet, 75 | "/variable_write", 76 | nil, 77 | http.StatusOK, 78 | }, 79 | { 80 | http.MethodPost, 81 | "/variable_write", 82 | struct { 83 | Board uint8 84 | Name string 85 | Type string 86 | Addr uint32 87 | }{ 88 | Board: 1, 89 | Name: "a", 90 | Type: "double", 91 | Addr: 0x20123456, 92 | }, 93 | http.StatusNoContent, 94 | }, 95 | { 96 | http.MethodGet, 97 | "/variable_write", 98 | nil, 99 | http.StatusOK, 100 | }, 101 | { 102 | http.MethodPut, 103 | "/variable_write", 104 | struct { 105 | Board uint8 106 | Name string 107 | Type string 108 | Addr uint32 109 | Data float64 110 | }{ 111 | Board: 1, 112 | Name: "a", 113 | Type: "double", 114 | Addr: 0x20123456, 115 | Data: 100, 116 | }, 117 | http.StatusNoContent, 118 | }, 119 | { 120 | http.MethodDelete, 121 | "/variable_write", 122 | struct { 123 | Board uint8 124 | Name string 125 | Type string 126 | Addr uint32 127 | }{ 128 | Board: 1, 129 | Name: "a", 130 | Type: "double", 131 | Addr: 0x20123456, 132 | }, 133 | http.StatusNoContent, 134 | }, 135 | } 136 | 137 | variableToWriteCtrl := makeVariableCtrl(variable.WR) 138 | 139 | ctrlerTest(variableToWriteCtrl, cases, t) 140 | } 141 | 142 | func TestVariableTypeCtrl(t *testing.T) { 143 | cases := casesT{ 144 | { 145 | http.MethodGet, 146 | "/serials", 147 | nil, 148 | http.StatusOK, 149 | }, 150 | { 151 | http.MethodPost, 152 | "/serials", 153 | nil, 154 | http.StatusMethodNotAllowed, 155 | }, 156 | } 157 | 158 | ctrlerTest(variableTypeCtrl, cases, t) 159 | } 160 | -------------------------------------------------------------------------------- /internal/server/websocket.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/golang/glog" 7 | "github.com/gorilla/websocket" 8 | 9 | "github.com/scutrobotlab/asuwave/internal/serial" 10 | "github.com/scutrobotlab/asuwave/pkg/elffile" 11 | ) 12 | 13 | func dataWebsocketCtrl(w http.ResponseWriter, r *http.Request) { 14 | var upgrader = websocket.Upgrader{ 15 | ReadBufferSize: 1024, 16 | WriteBufferSize: 1024, 17 | CheckOrigin: func(r *http.Request) bool { 18 | return true 19 | }, 20 | } 21 | c, err := upgrader.Upgrade(w, r, nil) 22 | if err != nil { 23 | glog.Errorln("upgrade:", err) 24 | return 25 | } 26 | defer c.Close() 27 | for { 28 | b := <-serial.Chch 29 | err = c.WriteMessage(websocket.TextMessage, []byte(b)) 30 | if err != nil { 31 | glog.Errorln("write:", err) 32 | break 33 | } 34 | } 35 | } 36 | 37 | func fileWebsocketCtrl(w http.ResponseWriter, r *http.Request) { 38 | var upgrader = websocket.Upgrader{ 39 | ReadBufferSize: 1024, 40 | WriteBufferSize: 1024, 41 | CheckOrigin: func(r *http.Request) bool { 42 | return true 43 | }, 44 | } 45 | c, err := upgrader.Upgrade(w, r, nil) 46 | if err != nil { 47 | glog.Errorln("upgrade:", err) 48 | return 49 | } 50 | defer c.Close() 51 | for { 52 | select { 53 | case file := <-elffile.ChFileWrite: 54 | glog.Infoln("filews got modified event:", file) 55 | err = c.WriteMessage(websocket.TextMessage, []byte(file)) 56 | if err != nil { 57 | glog.Errorln("write:", err) 58 | break 59 | } 60 | case err, ok := <-elffile.ChFileError: 61 | if !ok { 62 | return 63 | } 64 | glog.Errorln("error:", err) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /internal/variable/chart.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | type ChartT struct { 4 | Board uint8 5 | Name string 6 | Data float64 7 | Tick uint32 8 | } 9 | -------------------------------------------------------------------------------- /internal/variable/const.go: -------------------------------------------------------------------------------- 1 | /* 2 | 想给你写封信,却不知从何说起 3 | */ 4 | package variable 5 | 6 | // 篇幅有限,也挡不住我的思念 7 | const NumMaxPacket = 5 8 | 9 | type ActMode uint8 10 | 11 | var TypeLen = map[string]int{ 12 | "uint8_t": 1, 13 | "uint16_t": 2, 14 | "uint32_t": 4, 15 | "uint64_t": 8, 16 | "int8_t": 1, 17 | "int16_t": 2, 18 | "int32_t": 4, 19 | "int64_t": 8, 20 | "int": 4, 21 | "float": 4, 22 | "double": 8, 23 | } 24 | 25 | const ( 26 | _ ActMode = iota 27 | Subscribe // 想让你一直联系 28 | SubscribeReturn // 期待你认真回应 29 | Unsubscribe // 却担心打扰到你 30 | UnsubscribeReturn // 有回复就够了呢 31 | Read // 你在想什么 32 | ReadReturn // 能告诉我吗 33 | Write // 希望改变你的心意 34 | WriteReturn // 传达到了吗 35 | ) 36 | 37 | // 你现在在哪里呢 38 | const ( 39 | _ = iota 40 | Board1 // 是近在咫尺 41 | Board2 // 还是远在天边 42 | Board3 43 | ) 44 | -------------------------------------------------------------------------------- /internal/variable/cvttype.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "math" 7 | ) 8 | 9 | func AnyToBytes(i interface{}) []byte { 10 | buf := bytes.NewBuffer([]byte{}) 11 | binary.Write(buf, binary.LittleEndian, i) 12 | return buf.Bytes() 13 | } 14 | 15 | func SpecFromBytes(vType string, data []byte) float64 { 16 | switch vType { 17 | case "uint8_t": 18 | return float64(BytesToUint8(data)) 19 | case "uint16_t": 20 | return float64(BytesToUint16(data)) 21 | case "uint32_t": 22 | return float64(BytesToUint32(data)) 23 | case "uint64_t": 24 | return float64(BytesToUint64(data)) 25 | case "int8_t": 26 | return float64(BytesToInt8(data)) 27 | case "int16_t": 28 | return float64(BytesToInt16(data)) 29 | case "int32_t", "int": 30 | return float64(BytesToInt32(data)) 31 | case "int64_t": 32 | return float64(BytesToInt64(data)) 33 | case "float": 34 | return float64(BytesToFloat32(data)) 35 | case "double": 36 | return float64(BytesToFloat64(data)) 37 | default: 38 | return 0 39 | } 40 | } 41 | func SpecToBytes(vType string, i float64) []byte { 42 | switch vType { 43 | case "uint8_t": 44 | return AnyToBytes(uint8(i)) 45 | case "uint16_t": 46 | return AnyToBytes(uint16(i)) 47 | case "uint32_t": 48 | return AnyToBytes(uint32(i)) 49 | case "uint64_t": 50 | return AnyToBytes(uint64(i)) 51 | case "int8_t": 52 | return AnyToBytes(int8(i)) 53 | case "int16_t": 54 | return AnyToBytes(int16(i)) 55 | case "int32_t", "int": 56 | return AnyToBytes(int32(i)) 57 | case "int64_t": 58 | return AnyToBytes(int64(i)) 59 | case "float": 60 | return AnyToBytes(float32(i)) 61 | case "double": 62 | return AnyToBytes(float64(i)) 63 | default: 64 | return AnyToBytes(i) 65 | } 66 | } 67 | 68 | func BytesToInt8(i []byte) int8 { 69 | var o int8 70 | buf := bytes.NewReader(i) 71 | binary.Read(buf, binary.LittleEndian, &o) 72 | return o 73 | } 74 | 75 | func BytesToInt16(i []byte) int16 { 76 | var o int16 77 | buf := bytes.NewReader(i) 78 | binary.Read(buf, binary.LittleEndian, &o) 79 | return o 80 | } 81 | 82 | func BytesToInt32(i []byte) int32 { 83 | var o int32 84 | buf := bytes.NewReader(i) 85 | binary.Read(buf, binary.LittleEndian, &o) 86 | return o 87 | } 88 | 89 | func BytesToInt64(i []byte) int64 { 90 | var o int64 91 | buf := bytes.NewReader(i) 92 | binary.Read(buf, binary.LittleEndian, &o) 93 | return o 94 | } 95 | 96 | func BytesToUint8(i []byte) uint8 { 97 | var o uint8 98 | buf := bytes.NewReader(i) 99 | binary.Read(buf, binary.LittleEndian, &o) 100 | return o 101 | } 102 | 103 | func BytesToUint16(i []byte) uint16 { 104 | var o uint16 105 | buf := bytes.NewReader(i) 106 | binary.Read(buf, binary.LittleEndian, &o) 107 | return o 108 | } 109 | 110 | func BytesToUint32(i []byte) uint32 { 111 | var o uint32 112 | buf := bytes.NewReader(i) 113 | binary.Read(buf, binary.LittleEndian, &o) 114 | return o 115 | } 116 | 117 | func BytesToUint64(i []byte) uint64 { 118 | var o uint64 119 | buf := bytes.NewReader(i) 120 | binary.Read(buf, binary.LittleEndian, &o) 121 | return o 122 | } 123 | 124 | func BytesToFloat32(bytes []byte) float32 { 125 | bits := binary.LittleEndian.Uint32(bytes) 126 | return math.Float32frombits(bits) 127 | } 128 | 129 | func BytesToFloat64(bytes []byte) float64 { 130 | bits := binary.LittleEndian.Uint64(bytes) 131 | return math.Float64frombits(bits) 132 | } 133 | -------------------------------------------------------------------------------- /internal/variable/cvttype_test.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | import "testing" 4 | 5 | func TestAnyToBytes(t *testing.T) { 6 | cases := []struct { 7 | in interface{} 8 | want []byte 9 | }{ 10 | {uint8(10), []byte{0x0a}}, 11 | {uint16(65534), []byte{0xfe, 0xff}}, 12 | {uint32(42949672), []byte{0x28, 0x5c, 0x8f, 0x02}}, 13 | {uint64(18446744073709551615), []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, 14 | {int8(-2), []byte{0xfe}}, 15 | {int16(-134), []byte{0x7a, 0xff}}, 16 | {int32(942949672), []byte{0x28, 0x45, 0x34, 0x38}}, 17 | {int64(8446744073709551615), []byte{0xff, 0xff, 0x17, 0x76, 0xfb, 0xdc, 0x38, 0x75}}, 18 | {float32(-8.25), []byte{0x00, 0x00, 0x04, 0xc1}}, 19 | {float64(13278.125), []byte{0x0, 0x0, 0x0, 0x0, 0x10, 0xef, 0xc9, 0x40}}, 20 | } 21 | for _, c := range cases { 22 | got := AnyToBytes(c.in) 23 | if string(got) != string(c.want) { 24 | t.Errorf("AnyToBytes(%#v) == %#v, want %#v", c.in, got, c.want) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/variable/datautil.go: -------------------------------------------------------------------------------- 1 | /**/ 2 | package variable 3 | 4 | // 把我的思念,化作一串珍珠送给你 5 | func MakeWriteCmd(v T) []byte { 6 | // 拾起一颗颗珍珠 7 | data := make([]byte, 16) 8 | 9 | // 刻上你所在的城市和我的思念 10 | data[0] = byte(v.Board) 11 | data[1] = byte(Write) 12 | data[2] = byte(TypeLen[v.Type]) 13 | copy(data[3:7], AnyToBytes(v.Addr)) 14 | 15 | copy(data[7:15], SpecToBytes(v.Type, v.Data)) 16 | 17 | // 终于完成了 18 | data[15] = '\n' 19 | 20 | // 传达给你吧 21 | return data 22 | } 23 | 24 | // 把我的思念,化作一串珍珠送给你 25 | func MakeCmd(act ActMode, v CmdT) []byte { 26 | 27 | // 拾起一颗颗珍珠 28 | data := make([]byte, 16) 29 | 30 | // 刻上你所在的城市和我的思念 31 | data[0] = byte(v.Board) 32 | data[1] = byte(act) 33 | data[2] = byte(v.Length) 34 | copy(data[3:7], AnyToBytes(v.Addr)) 35 | 36 | // 终于完成了 37 | data[15] = '\n' 38 | 39 | // 传达给你吧 40 | return data 41 | } 42 | -------------------------------------------------------------------------------- /internal/variable/datautil_test.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | // import ( 4 | // "testing" 5 | 6 | // "github.com/scutrobotlab/asuwave/internal/variable" 7 | // ) 8 | 9 | // func TestMakeWriteCmd(t *testing.T) { 10 | // cases := []struct { 11 | // v T 12 | // want []byte 13 | // }{ 14 | // { 15 | // T{ 16 | // Board: 1, 17 | // Name: "a", 18 | // Type: "float", 19 | // Addr: 0x20123456, 20 | // Data: -8.25, 21 | // Tick: 0, 22 | // }, 23 | // []byte{0x1, 0x7, 0x4, 0x56, 0x34, 0x12, 0x20, 0x0, 0x0, 0x4, 0xc1, 0x0, 0x0, 0x0, 0x0, 0xa}, 24 | // }, 25 | // } 26 | 27 | // for _, c := range cases { 28 | // got := MakeWriteCmd(c.v) 29 | // if string(got) != string(c.want) { 30 | // t.Errorf("\nmakeWriteCmd(%#v)\n\thave: %#v\n\twant: %#v", c.v, got, c.want) 31 | // } 32 | // } 33 | // } 34 | 35 | // func TestMakeCmd(t *testing.T) { 36 | // cases := []struct { 37 | // v CmdT 38 | // act ActMode 39 | // want []byte 40 | // }{ 41 | // { 42 | // CmdT{ 43 | // Board: 1, 44 | // Length: 4, 45 | // Addr: 0x20123456, 46 | // }, 47 | // Subscribe, 48 | // []byte{0x1, 0x1, 0x4, 0x56, 0x34, 0x12, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa}, 49 | // }, 50 | // { 51 | // CmdT{ 52 | // Board: 1, 53 | // Length: 4, 54 | // Addr: 0x20123456, 55 | // }, 56 | // Unsubscribe, 57 | // []byte{0x1, 0x3, 0x4, 0x56, 0x34, 0x12, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa}, 58 | // }, 59 | // } 60 | 61 | // for _, c := range cases { 62 | // got := MakeCmd(c.act, c.v) 63 | // if string(got) != string(c.want) { 64 | // t.Errorf("\nmakeCmd(%#v, %d)\n\thave: %#v\n\twant: %#v", c.v, c.act, got, c.want) 65 | // } 66 | // } 67 | // } 68 | 69 | // func TestFindValidPart(t *testing.T) { 70 | // cases := []struct { 71 | // data []byte 72 | // startIdx int 73 | // endIdx int 74 | // }{ 75 | // { 76 | // data: []byte{ 77 | // 0x56, 0x34, 0x12, 0x80, 78 | // 0x00, 0x00, 0x04, 0xc1, 0x01, 0x02, 0x00, 0x00, 79 | // 0x00, 0x00, 0x00, 0x01, 80 | // }, 81 | // startIdx: 0, 82 | // endIdx: 0, 83 | // }, 84 | 85 | // { 86 | // data: []byte{ 87 | // 0x01, 0x02, 0x04, 88 | // 0x56, 0x34, 0x12, 0x80, 89 | // 0x00, 0x00, 0x04, 0xc1, 0x00, 0x00, 0x00, 0x00, 90 | // 0x00, 0x00, 0x00, 0x01, 91 | // 0x0a, 92 | // }, 93 | // startIdx: 0, 94 | // endIdx: 20, 95 | // }, 96 | // { 97 | // data: []byte{ 98 | // 0x01, 0x02, 0x04, 99 | // 0x56, 0x34, 0x12, 0x80, 100 | // 0x00, 0x00, 0x04, 0xc1, 0x00, 0x00, 0x00, 0x00, 101 | // 0x00, 0x00, 0x00, 0x01, 102 | // 0x0a, 103 | // 0x01, 0x02, 0x04, 104 | // 0x56, 0x34, 0x12, 0x80, 105 | // 0x00, 0x00, 0x04, 0xc1, 0x00, 0x00, 0x00, 0x00, 106 | // 0x00, 0x00, 0x00, 0x01, 107 | // 0x0a, 108 | // }, 109 | // startIdx: 0, 110 | // endIdx: 40, 111 | // }, 112 | 113 | // { 114 | // data: []byte{ 115 | // 0x56, 0x34, 0x12, 0x80, 116 | // 0x00, 0x00, 0x04, 0xc1, 0x00, 0x00, 0x00, 0x00, 117 | // 0x00, 0x00, 0x00, 0x01, 118 | // 0x0a, 119 | 120 | // 0x01, 0x02, 0x04, 121 | // 0x56, 0x34, 0x12, 0x80, 122 | // 0x00, 0x00, 0x04, 0xc1, 0x00, 0x00, 0x00, 0x00, 123 | // 0x00, 0x00, 0x00, 0x01, 124 | // 0x0a, 125 | 126 | // 0x01, 0x02, 0x04, 127 | // }, 128 | // startIdx: 17, 129 | // endIdx: 37, 130 | // }, 131 | 132 | // { 133 | // data: []byte{ 134 | // 0x00, 0x00, 0x01, 0x02, 135 | // 0x0a, 136 | 137 | // 0x01, 0x02, 0x04, 138 | // 0x56, 0x34, 0x12, 0x80, 139 | // 0x00, 0x00, 0x04, 0xc1, 0x00, 0x00, 0x00, 0x00, 140 | // 0x00, 0x00, 0x00, 0x01, 141 | // 0x0a, 142 | 143 | // 0x00, 0x0a, 0x01, 0x02, 144 | // }, 145 | // startIdx: 5, 146 | // endIdx: 25, 147 | // }, 148 | // { 149 | // data: []byte{ 150 | // 0xa, 151 | 152 | // 0x1, 0x2, 0x4, 153 | // 0x30, 0x4, 0x0, 0x20, 154 | // 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 155 | // 0x41, 0xe2, 0x0, 0x0, 156 | // 0xa, 157 | 158 | // 0x1, 0x2, 0x4, 159 | // 0xa8, 0x4, 0x0, 0x20, 160 | // 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 161 | // 0x41, 0xe2, 0x0, 0x0, 162 | // 0xa, 163 | 164 | // 0x1, 0x2, 0x4, 165 | // 0xac, 0x1, 0x0, 0x20, 166 | // 0x0, 0x0, 0xb4, 0xbe, 0x0, 0x0, 0x0, 0x0, 167 | // 0x4b, 0xe2, 0x0, 0x0, 168 | // 0xa, 169 | 170 | // 0x1, 0x2, 0x4, 171 | // 0x40, 0x3, 0x0, 0x20, 172 | // 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 173 | // 0x4b, 0xe2, 0x0, 0x0, 174 | // 0xa, 175 | 176 | // 0x1, 0x2, 0x4, 177 | // 0xb8, 0x3, 0x0, 0x20, 178 | // 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 179 | // 0x4b, 0xe2, 0x0, 0x0, 180 | // 0xa, 181 | 182 | // 0x1, 0x2, 0x4, 183 | // 0x30, 0x4, 0x0, 0x20, 184 | // 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 185 | // 0x4b, 0xe2, 0x0, 0x0, 186 | // 0xa, 187 | 188 | // 0x1, 0x2, 0x4, 189 | // 0xa8, 0x4, 0x0, 0x20, 190 | // 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 191 | // 0x4b, 0xe2, 0x0, 0x0, 192 | // 0xa, 193 | 194 | // 0x1, 0x2, 0x4, 195 | // 0xac, 0x1, 0x0, 0x20, 0x0, 0x80, 0x9d, 0xbe, 196 | // 0x0, 0x0, 0x0, 0x0, 197 | // 0x55, 0xe2, 0x0, 0x0, 198 | // }, 199 | // startIdx: 1, 200 | // endIdx: 141, 201 | // }, 202 | // } 203 | // for _, c := range cases { 204 | // s, n := Unpack(c.data) 205 | // if s != c.startIdx || n != c.endIdx { 206 | // t.Errorf("VerifyBuff(%#v) == %#v,%#v want %#v,%#v", c.data, s, n, c.startIdx, c.endIdx) 207 | // } 208 | // } 209 | // } 210 | -------------------------------------------------------------------------------- /internal/variable/filt.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | import ( 4 | "github.com/golang/glog" 5 | "github.com/scutrobotlab/asuwave/pkg/slip" 6 | ) 7 | 8 | type CmdT struct { 9 | Board uint8 10 | Length int 11 | Addr uint32 12 | Tick uint32 13 | Data [8]byte 14 | } 15 | 16 | // 在如山的信笺里,找寻变量的回音 17 | func Unpack(data []byte) ([]CmdT, []byte) { 18 | 19 | // 分开信笺 20 | ends := []int{} 21 | for i, d := range data { 22 | if d == slip.END { 23 | ends = append(ends, i) 24 | } 25 | } 26 | 27 | if len(ends) == 0 { // 若是无言 28 | return []CmdT{}, []byte{} 29 | } 30 | 31 | vars := []CmdT{} 32 | 33 | // 探寻每一封信 34 | for i := 1; i < len(ends); i++ { 35 | // 此信浅薄,难载深情 36 | if ends[i]-ends[i-1] < 20 { 37 | continue 38 | } 39 | // 解开此信 40 | glog.Infoln(data[ends[i-1] : ends[i]+1]) 41 | pack, err := slip.Unpack(data[ends[i-1] : ends[i]+1]) 42 | if err != nil { 43 | glog.Errorln(err.Error()) 44 | continue 45 | } 46 | glog.Infoln(pack) 47 | // 非变量之回音,或无合法之落款,则弃之 48 | for len(pack) != 20 || ActMode(pack[1]) != SubscribeReturn || pack[19] != '\n' { 49 | glog.V(1).Infoln("Not Subscribereturn pack", pack) 50 | continue 51 | } 52 | // 聆听变量的回音 53 | v := CmdT{ 54 | Board: pack[0], 55 | Length: int(pack[2]), 56 | Addr: BytesToUint32(pack[3:7]), 57 | Data: *(*[8]byte)(pack[7:15]), 58 | Tick: BytesToUint32(pack[15:19]), 59 | } 60 | // 加入变量列表 61 | vars = append(vars, v) 62 | } 63 | f := ends[len(ends)-1] // 最后的结束, 64 | newbuff := make([]byte, len(data)-f) // 也是新的开始。 65 | if len(newbuff) == 0 { 66 | return vars, newbuff 67 | } 68 | copy(newbuff, data[f:]) // 留存残篇。 69 | return vars, newbuff // 变量的回音,仍有余音 70 | } 71 | 72 | // 从茫茫 vars 中,寻找我所挂念的 to[RD] ,记录在列表 chart 中。 73 | // 所有的 add 我都难以忘记,所有的 del 我都不愿提起 74 | func Filt(vars []CmdT) (chart []ChartT, add []CmdT, del []CmdT) { 75 | to[RD].RLock() 76 | defer to[RD].RUnlock() 77 | 78 | chart = []ChartT{} 79 | add = []CmdT{} // 有些变量,我难以忘记 80 | del = []CmdT{} // 有些变量,我不愿提起 81 | 82 | addrs := map[uint32]bool{} 83 | 84 | for _, v := range vars { 85 | // 它是我要找的那个变量吗? 86 | if r, ok := to[RD].m[v.Addr]; ok { // 是的,我还挂念着它 87 | r.Tick = v.Tick 88 | r.Data = SpecFromBytes(r.Type, v.Data[:]) 89 | chart = append(chart, ChartT{ 90 | Board: r.Board, 91 | Name: r.Name, 92 | Data: r.SignalGain*r.Data + r.SignalBias, 93 | Tick: r.Tick, 94 | }) 95 | } else { // 不是的,请忘了它 96 | del = append(del, v) 97 | } 98 | } 99 | 100 | // 我所挂念的,它们都还在吗 101 | for _, r := range to[RD].m { 102 | if _, ok := addrs[r.Addr]; !ok { 103 | // 我很想它,下次请别忘记 104 | add = append(add, CmdT{ 105 | Board: r.Board, 106 | Length: TypeLen[r.Type], 107 | Addr: r.Addr, 108 | }) 109 | } 110 | } 111 | return 112 | } 113 | -------------------------------------------------------------------------------- /internal/variable/filt_test.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | // import ( 4 | // "fmt" 5 | // "testing" 6 | // ) 7 | 8 | // func TestFilt(t *testing.T) { 9 | // cases := []struct { 10 | // in []byte 11 | // listV map[uint32]T 12 | // wantChart []ChartT 13 | // wantAdd []CmdT 14 | // wantDel []CmdT 15 | // }{ 16 | // { 17 | // in: []byte{ 18 | // 0x01, 0x02, 0x04, 19 | // 0x56, 0x34, 0x12, 0x80, 20 | // 0x00, 0x00, 0x04, 0xc1, 0x00, 0x00, 0x00, 0x00, 21 | // 0x01, 0x00, 0x00, 0x00, 22 | // 0x0a, 23 | // }, 24 | // listV: map[uint32]T{ 25 | // 0x80123456: { 26 | // Board: 1, 27 | // Name: "a", 28 | // Type: "float", 29 | // Addr: 0x80123456, 30 | // SignalGain: 1, 31 | // }, 32 | // 0x80654321: { 33 | // Board: 1, 34 | // Name: "b", 35 | // Type: "int", 36 | // Addr: 0x80654321, 37 | // SignalGain: 1, 38 | // }, 39 | // }, 40 | // wantChart: []ChartT{ 41 | // { 42 | // Board: 1, 43 | // Name: "a", 44 | // Data: -8.25, 45 | // Tick: 1, 46 | // }, 47 | // }, 48 | // wantAdd: []CmdT{ 49 | // { 50 | // Board: 1, 51 | // Length: 4, 52 | // Addr: 0x80654321, 53 | // }, 54 | // }, 55 | // wantDel: []CmdT{}, 56 | // }, 57 | 58 | // { 59 | // in: []byte{ 60 | // 0x01, 0x02, 0x04, 61 | // 0x56, 0x34, 0x12, 0x80, 62 | // 0x00, 0x00, 0x04, 0xc1, 0x00, 0x00, 0x00, 0x00, 63 | // 0x01, 0x00, 0x00, 0x00, 64 | // 0x0a, 65 | // 0x01, 0x02, 0x04, 66 | // 0x21, 0x43, 0x65, 0x80, 67 | // 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 68 | // 0x01, 0x00, 0x00, 0x00, 69 | // 0x0a, 70 | // }, 71 | // listV: map[uint32]T{ 72 | // 0x80123456: { 73 | // Board: 1, 74 | // Name: "a", 75 | // Type: "float", 76 | // Addr: 0x80123456, 77 | // SignalGain: 1, 78 | // }, 79 | // }, 80 | // wantChart: []ChartT{ 81 | // { 82 | // Board: 1, 83 | // Name: "a", 84 | // Data: -8.25, 85 | // Tick: 1, 86 | // }, 87 | // }, 88 | // wantAdd: []CmdT{}, 89 | // wantDel: []CmdT{ 90 | // { 91 | // Board: 1, 92 | // Length: 4, 93 | // Addr: 0x80654321, 94 | // }, 95 | // }, 96 | // }, 97 | // } 98 | // for _, c := range cases { 99 | // SetAll(RD, c.listV) 100 | // gotChart, gotAdd, gotDel := Filt(c.in) 101 | // if ok, msg := assertEQ(gotChart, c.wantChart); !ok { 102 | // t.Errorf("Chart list " + msg) 103 | // } 104 | // if ok, msg := assertEQ(gotAdd, c.wantAdd); !ok { 105 | // t.Errorf("Add list " + msg) 106 | // } 107 | // if ok, msg := assertEQ(gotDel, c.wantDel); !ok { 108 | // t.Errorf("Del list " + msg) 109 | // } 110 | // } 111 | // } 112 | 113 | // func assertEQ[T ChartT | CmdT](a []T, b []T) (bool, string) { 114 | // if len(a) != len(b) { 115 | // return false, fmt.Sprint("Different length", len(a), len(b)) 116 | // } 117 | // for i := range a { 118 | // if a[i] != b[i] { 119 | // return false, fmt.Sprintf("Different at %d: %v != %v", i, a[i], b[i]) 120 | // } 121 | // } 122 | // return true, "" 123 | // } 124 | -------------------------------------------------------------------------------- /internal/variable/opt_json.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | import ( 4 | "path" 5 | 6 | "github.com/golang/glog" 7 | "github.com/scutrobotlab/asuwave/internal/helper" 8 | "github.com/scutrobotlab/asuwave/pkg/jsonfile" 9 | ) 10 | 11 | var ( 12 | jsonPath = map[Mod]string{ 13 | RD: path.Join(helper.AppConfigDir(), "vToRead.json"), 14 | WR: path.Join(helper.AppConfigDir(), "vToWrite.json"), 15 | } 16 | optSaveVarList bool 17 | optUpdateByProj bool 18 | ) 19 | 20 | // Only call by option. 21 | func SetOptSaveVarList(v bool) { 22 | if optSaveVarList == v { 23 | glog.V(1).Infof("SaveVarList has set to %t, skip\n", v) 24 | return 25 | } 26 | glog.V(1).Infof("Set SaveVarList to %t\n", v) 27 | jsonfile.Save(jsonPath[RD], to[RD].m) 28 | jsonfile.Save(jsonPath[WR], to[WR].m) 29 | optSaveVarList = v 30 | } 31 | 32 | // Only call by option. 33 | func SetOptUpdateByProj(v bool) { 34 | if optUpdateByProj == v { 35 | glog.V(1).Infof("UpdateByProj has set to %t, skip\n", v) 36 | return 37 | } 38 | glog.V(1).Infof("Set UpdateByProj to %t\n", v) 39 | optUpdateByProj = v 40 | } 41 | 42 | // Only call by option. 43 | func GetOptSaveVarList() bool { 44 | return optSaveVarList 45 | } 46 | 47 | // Only call by option. 48 | func GetOptUpdateByProj() bool { 49 | return optUpdateByProj 50 | } 51 | 52 | func JsonLoadAll() { 53 | to[RD].Lock() 54 | defer to[RD].Unlock() 55 | to[WR].Lock() 56 | defer to[WR].Unlock() 57 | 58 | jsonfile.Load(jsonPath[RD], &to[RD].m) 59 | jsonfile.Load(jsonPath[WR], &to[WR].m) 60 | glog.Infoln(jsonPath[RD], "load success.") 61 | glog.Infoln(jsonPath[WR], "load success.") 62 | 63 | jsonfile.Save(jsonPath[RD], to[RD].m) 64 | jsonfile.Save(jsonPath[WR], to[WR].m) 65 | } 66 | -------------------------------------------------------------------------------- /internal/variable/proj.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | import ( 4 | "encoding/json" 5 | "sync" 6 | ) 7 | 8 | type ProjT struct { 9 | Addr string 10 | Name string 11 | Type string 12 | } 13 | 14 | type Projs map[string]ProjT 15 | 16 | type projMapType struct { // 一个读写锁保护的线程安全的map 17 | sync.RWMutex // 读写锁保护下面的map字段 18 | m Projs 19 | } 20 | 21 | var toProj projMapType = projMapType{ 22 | m: make(Projs, 0), 23 | } 24 | 25 | func SetAllProj(m Projs) { 26 | toProj.Lock() // 锁保护 27 | defer toProj.Unlock() 28 | toProj.m = m 29 | } 30 | 31 | // 以json格式获取所有Proj变量 32 | func GetAllProj() ([]byte, error) { 33 | toProj.RLock() // 锁保护 34 | defer toProj.RUnlock() 35 | return json.Marshal(toProj.m) 36 | } 37 | 38 | func GetProj(k string) (ProjT, bool) { //从map中读取一个值 39 | toProj.RLock() 40 | defer toProj.RUnlock() 41 | v, existed := toProj.m[k] // 在锁的保护下从map中读取 42 | return v, existed 43 | } 44 | 45 | func SetProj(k string, v ProjT) { // 设置一个键值对 46 | toProj.Lock() // 锁保护 47 | defer toProj.Unlock() 48 | toProj.m[k] = v 49 | } 50 | 51 | func DeleteProj(k string) { //删除一个键 52 | toProj.Lock() // 锁保护 53 | defer toProj.Unlock() 54 | delete(toProj.m, k) 55 | } 56 | -------------------------------------------------------------------------------- /internal/variable/read_write.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | import ( 4 | "encoding/json" 5 | "sync" 6 | 7 | "github.com/scutrobotlab/asuwave/pkg/jsonfile" 8 | ) 9 | 10 | type Mod int 11 | 12 | const ( 13 | RD Mod = iota 14 | WR 15 | ) 16 | 17 | type T struct { 18 | Board uint8 19 | Name string 20 | Type string 21 | Addr uint32 22 | Data float64 23 | Tick uint32 24 | Inputcolor string 25 | SignalGain float64 26 | SignalBias float64 27 | } 28 | 29 | type RWMap struct { // 一个读写锁保护的线程安全的map 30 | sync.RWMutex // 读写锁保护下面的map字段 31 | m map[uint32]T 32 | } 33 | 34 | var to []RWMap = []RWMap{{ 35 | m: make(map[uint32]T, 0), 36 | }, { 37 | m: make(map[uint32]T, 0), 38 | }} 39 | 40 | func SetAll(o Mod, v map[uint32]T) { 41 | to[o].Lock() // 锁保护 42 | defer to[o].Unlock() 43 | to[o].m = v 44 | jsonfile.Save(jsonPath[o], to[o].m) 45 | } 46 | 47 | // 以json格式获取所有Mod变量 48 | func GetAll(o Mod) ([]byte, error) { 49 | to[o].RLock() // 锁保护 50 | defer to[o].RUnlock() 51 | return json.Marshal(to[o].m) 52 | } 53 | 54 | func GetKeys(o Mod) (keys []uint32) { 55 | to[o].RLock() // 锁保护 56 | defer to[o].RUnlock() 57 | for k := range to[o].m { 58 | keys = append(keys, k) 59 | } 60 | return 61 | } 62 | 63 | func Get(o Mod, k uint32) (T, bool) { //从map中读取一个值 64 | to[o].RLock() 65 | defer to[o].RUnlock() 66 | v, existed := to[o].m[k] // 在锁的保护下从map中读取 67 | return v, existed 68 | } 69 | 70 | func Set(o Mod, k uint32, v T) { // 设置一个键值对 71 | to[o].Lock() // 锁保护 72 | defer to[o].Unlock() 73 | to[o].m[k] = v 74 | jsonfile.Save(jsonPath[o], to[o].m) 75 | } 76 | 77 | func Delete(o Mod, k uint32) { //删除一个键 78 | to[o].Lock() // 锁保护 79 | defer to[o].Unlock() 80 | delete(to[o].m, k) 81 | jsonfile.Save(jsonPath[o], to[o].m) 82 | } 83 | -------------------------------------------------------------------------------- /internal/variable/variable.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/golang/glog" 7 | ) 8 | 9 | // 通过Proj的变量名更新Read和Write的地址和类型 10 | func UpdateByProj() { 11 | toProj.RLock() 12 | defer toProj.RUnlock() 13 | { 14 | to[RD].Lock() 15 | defer to[RD].Unlock() 16 | NewToRead := to[RD].m 17 | for k, v := range to[RD].m { 18 | if p, ok := toProj.m[v.Name]; ok { 19 | addr, err := strconv.ParseUint(p.Addr, 16, 32) 20 | if err != nil { 21 | glog.Errorln(err.Error()) 22 | continue 23 | } 24 | v.Addr = uint32(addr) 25 | v.Type = p.Type 26 | delete(NewToRead, k) 27 | NewToRead[v.Addr] = v 28 | } 29 | } 30 | to[RD].m = NewToRead 31 | } 32 | { 33 | to[WR].Lock() 34 | defer to[WR].Unlock() 35 | NewToModi := to[WR].m 36 | for k, v := range to[WR].m { 37 | if p, ok := toProj.m[v.Name]; ok { 38 | addr, err := strconv.ParseUint(p.Addr, 16, 32) 39 | if err != nil { 40 | glog.Errorln(err.Error()) 41 | continue 42 | } 43 | v.Addr = uint32(addr) 44 | v.Type = p.Type 45 | delete(NewToModi, k) 46 | NewToModi[v.Addr] = v 47 | } 48 | } 49 | to[WR].m = NewToModi 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /log/asuwave.exe.LAPTOP-KLOCKHJ1.LAPTOP-KLOCKHJ1_bige.log.INFO.20220516-204625.8312: -------------------------------------------------------------------------------- 1 | Log file created at: 2022/05/16 20:46:25 2 | Running on machine: LAPTOP-KLOCKHJ1 3 | Binary: Built with gc go1.18.1 for windows/amd64 4 | Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg 5 | I0516 20:46:25.318923 8312 jsonfile.go:27] C:\Users\bige\AppData\Roaming/asuwave/option.json Found 6 | I0516 20:46:25.327681 8312 opt_json.go:26] Set SaveVarList to true 7 | I0516 20:46:25.328191 8312 jsonfile.go:19] C:\Users\bige\AppData\Roaming/asuwave/vToRead.json save success. 8 | I0516 20:46:25.328736 8312 jsonfile.go:19] C:\Users\bige\AppData\Roaming/asuwave/vToWrite.json save success. 9 | I0516 20:46:25.328736 8312 opt_json.go:38] Set UpdateByProj to true 10 | I0516 20:46:25.328736 8312 jsonfile.go:27] C:\Users\bige\AppData\Roaming/asuwave/FileWatch.json Found 11 | I0516 20:46:25.329287 8312 watch.go:20] Get: [] 12 | I0516 20:46:25.330158 8312 jsonfile.go:19] C:\Users\bige\AppData\Roaming/asuwave/FileWatch.json save success. 13 | I0516 20:46:25.330695 8312 jsonfile.go:19] C:\Users\bige\AppData\Roaming/asuwave/option.json save success. 14 | I0516 20:46:25.330695 8312 server.go:20] Listen on :8000 15 | I0516 20:46:25.331223 8312 serial.go:194] GrTransmit: 16 | I0516 20:46:31.905205 8312 server.go:52] [::1]:59751 POST /serial_cur 17 | I0516 20:46:31.905800 8312 testport.go:44] TestPort open at: 2022-05-16 20:46:25.3189231 +0800 CST m=+0.007091801 18 | I0516 20:46:31.906419 8312 serial.go:169] chOp... 19 | I0516 20:46:31.906933 8312 serial.go:177] GrReceive: default... 20 | I0516 20:46:32.036068 8312 serial.go:263] GrRxPrase: time after 200ms... 21 | I0516 20:46:32.036516 8312 serial.go:266] add: [] 22 | I0516 20:46:32.238168 8312 serial.go:263] GrRxPrase: time after 200ms... 23 | I0516 20:46:32.238802 8312 serial.go:266] add: [] 24 | I0516 20:46:32.441191 8312 serial.go:263] GrRxPrase: time after 200ms... 25 | I0516 20:46:32.442352 8312 serial.go:266] add: [] 26 | I0516 20:46:32.659312 8312 serial.go:263] GrRxPrase: time after 200ms... 27 | I0516 20:46:32.659788 8312 serial.go:266] add: [] 28 | I0516 20:46:32.862228 8312 serial.go:263] GrRxPrase: time after 200ms... 29 | I0516 20:46:32.862363 8312 serial.go:266] add: [] 30 | I0516 20:46:33.065149 8312 serial.go:263] GrRxPrase: time after 200ms... 31 | I0516 20:46:33.065716 8312 serial.go:266] add: [] 32 | I0516 20:46:33.267897 8312 serial.go:263] GrRxPrase: time after 200ms... 33 | I0516 20:46:33.268538 8312 serial.go:266] add: [] 34 | I0516 20:46:33.469695 8312 serial.go:263] GrRxPrase: time after 200ms... 35 | I0516 20:46:33.470654 8312 serial.go:266] add: [] 36 | I0516 20:46:33.671841 8312 serial.go:263] GrRxPrase: time after 200ms... 37 | I0516 20:46:33.671841 8312 serial.go:266] add: [] 38 | I0516 20:46:33.875027 8312 serial.go:263] GrRxPrase: time after 200ms... 39 | I0516 20:46:33.875717 8312 serial.go:266] add: [] 40 | I0516 20:46:34.078649 8312 serial.go:263] GrRxPrase: time after 200ms... 41 | I0516 20:46:34.079651 8312 serial.go:266] add: [] 42 | I0516 20:46:34.284029 8312 serial.go:263] GrRxPrase: time after 200ms... 43 | I0516 20:46:34.284557 8312 serial.go:266] add: [] 44 | I0516 20:46:34.485028 8312 serial.go:263] GrRxPrase: time after 200ms... 45 | I0516 20:46:34.485028 8312 serial.go:266] add: [] 46 | I0516 20:46:34.700694 8312 serial.go:263] GrRxPrase: time after 200ms... 47 | I0516 20:46:34.701857 8312 serial.go:266] add: [] 48 | I0516 20:46:34.903667 8312 serial.go:263] GrRxPrase: time after 200ms... 49 | I0516 20:46:34.904802 8312 serial.go:266] add: [] 50 | I0516 20:46:35.108481 8312 serial.go:263] GrRxPrase: time after 200ms... 51 | I0516 20:46:35.108481 8312 serial.go:266] add: [] 52 | I0516 20:46:35.310956 8312 serial.go:263] GrRxPrase: time after 200ms... 53 | I0516 20:46:35.311051 8312 serial.go:266] add: [] 54 | I0516 20:46:35.512534 8312 serial.go:263] GrRxPrase: time after 200ms... 55 | I0516 20:46:35.514287 8312 serial.go:266] add: [] 56 | I0516 20:46:35.730834 8312 serial.go:263] GrRxPrase: time after 200ms... 57 | I0516 20:46:35.732248 8312 serial.go:266] add: [] 58 | I0516 20:46:35.948226 8312 serial.go:263] GrRxPrase: time after 200ms... 59 | I0516 20:46:35.948226 8312 serial.go:266] add: [] 60 | I0516 20:46:36.151686 8312 serial.go:263] GrRxPrase: time after 200ms... 61 | I0516 20:46:36.151794 8312 serial.go:266] add: [] 62 | I0516 20:46:36.354983 8312 serial.go:263] GrRxPrase: time after 200ms... 63 | I0516 20:46:36.355592 8312 serial.go:266] add: [] 64 | I0516 20:46:36.557329 8312 serial.go:263] GrRxPrase: time after 200ms... 65 | I0516 20:46:36.557380 8312 serial.go:266] add: [] 66 | I0516 20:46:36.758779 8312 serial.go:263] GrRxPrase: time after 200ms... 67 | I0516 20:46:36.759050 8312 serial.go:266] add: [] 68 | I0516 20:46:36.965070 8312 serial.go:263] GrRxPrase: time after 200ms... 69 | I0516 20:46:36.965859 8312 serial.go:266] add: [] 70 | I0516 20:46:37.181962 8312 serial.go:263] GrRxPrase: time after 200ms... 71 | I0516 20:46:37.182027 8312 serial.go:266] add: [] 72 | I0516 20:46:37.383102 8312 serial.go:263] GrRxPrase: time after 200ms... 73 | I0516 20:46:37.383906 8312 serial.go:266] add: [] 74 | I0516 20:46:37.585980 8312 serial.go:263] GrRxPrase: time after 200ms... 75 | I0516 20:46:37.586272 8312 serial.go:266] add: [] 76 | I0516 20:46:37.789358 8312 serial.go:263] GrRxPrase: time after 200ms... 77 | I0516 20:46:37.789787 8312 serial.go:266] add: [] 78 | I0516 20:46:37.991618 8312 serial.go:263] GrRxPrase: time after 200ms... 79 | I0516 20:46:37.991800 8312 serial.go:266] add: [] 80 | I0516 20:46:38.193340 8312 serial.go:263] GrRxPrase: time after 200ms... 81 | I0516 20:46:38.193340 8312 serial.go:266] add: [] 82 | I0516 20:46:38.398070 8312 serial.go:263] GrRxPrase: time after 200ms... 83 | I0516 20:46:38.398070 8312 serial.go:266] add: [] 84 | I0516 20:46:38.600907 8312 serial.go:263] GrRxPrase: time after 200ms... 85 | I0516 20:46:38.601731 8312 serial.go:266] add: [] 86 | I0516 20:46:38.804611 8312 serial.go:263] GrRxPrase: time after 200ms... 87 | I0516 20:46:38.805186 8312 serial.go:266] add: [] 88 | I0516 20:46:39.007430 8312 serial.go:263] GrRxPrase: time after 200ms... 89 | I0516 20:46:39.008337 8312 serial.go:266] add: [] 90 | I0516 20:46:39.213148 8312 serial.go:263] GrRxPrase: time after 200ms... 91 | I0516 20:46:39.214076 8312 serial.go:266] add: [] 92 | I0516 20:46:39.416793 8312 serial.go:263] GrRxPrase: time after 200ms... 93 | I0516 20:46:39.416793 8312 serial.go:266] add: [] 94 | I0516 20:46:39.620344 8312 serial.go:263] GrRxPrase: time after 200ms... 95 | I0516 20:46:39.620491 8312 serial.go:266] add: [] 96 | I0516 20:46:39.835547 8312 serial.go:263] GrRxPrase: time after 200ms... 97 | I0516 20:46:39.835547 8312 serial.go:266] add: [] 98 | I0516 20:46:40.052549 8312 serial.go:263] GrRxPrase: time after 200ms... 99 | I0516 20:46:40.053813 8312 serial.go:266] add: [] 100 | I0516 20:46:40.108263 8312 server.go:52] [::1]:59751 POST /variable_read 101 | I0516 20:46:40.109113 8312 jsonfile.go:19] C:\Users\bige\AppData\Roaming/asuwave/vToRead.json save success. 102 | I0516 20:46:40.122661 8312 server.go:52] [::1]:59751 GET /variable_read 103 | I0516 20:46:40.270033 8312 serial.go:263] GrRxPrase: time after 200ms... 104 | I0516 20:46:40.271070 8312 serial.go:266] add: [{1 8 556998961 0 [0 0 0 0 0 0 0 0]}] 105 | I0516 20:46:40.272756 8312 serial.go:159] Send cmd 1 {1 8 556998961 0 [0 0 0 0 0 0 0 0]} 106 | I0516 20:46:40.273839 8312 serial.go:196] GrTransmit: got chTx... 107 | I0516 20:46:40.274991 8312 serial.go:101] serial port write: [1 1 8 49 33 51 33 0 0 0 0 0 0 0 0 10] 108 | I0516 20:46:40.276084 8312 testport.go:141] Got write: board = 1 109 | I0516 20:46:40.276746 8312 testport.go:143] Got write: act = 1 110 | I0516 20:46:40.278658 8312 testport.go:145] Got write: length = 8 111 | I0516 20:46:40.280915 8312 testport.go:147] Got write: address = 556998961 112 | I0516 20:46:40.285145 8312 serial.go:194] GrTransmit: 113 | I0516 20:46:40.488225 8312 serial.go:263] GrRxPrase: time after 200ms... 114 | I0516 20:46:40.489202 8312 serial.go:266] add: [{1 8 556998961 0 [0 0 0 0 0 0 0 0]}] 115 | I0516 20:46:40.490118 8312 serial.go:144] Has sent subscribe cmd recently {1 8 556998961 0 [0 0 0 0 0 0 0 0]} 116 | I0516 20:46:40.706251 8312 serial.go:263] GrRxPrase: time after 200ms... 117 | I0516 20:46:40.706467 8312 serial.go:266] add: [{1 8 556998961 0 [0 0 0 0 0 0 0 0]}] 118 | I0516 20:46:40.708376 8312 serial.go:144] Has sent subscribe cmd recently {1 8 556998961 0 [0 0 0 0 0 0 0 0]} 119 | I0516 20:46:40.785319 8312 testport.go:155] Adding address: 21332131 120 | I0516 20:46:40.785319 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 106 60 0 0 10 192] 121 | I0516 20:46:40.786535 8312 serial.go:184] GrReceive: send chRx... 122 | I0516 20:46:40.786625 8312 serial.go:210] GrRxPrase: got chRx... 123 | I0516 20:46:40.787145 8312 serial.go:212] had buff: [] 124 | I0516 20:46:40.787691 8312 serial.go:216] got buff: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 106 60 0 0 10 192] 125 | I0516 20:46:40.787691 8312 filt.go:40] [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 106 60 0 0 10 192] 126 | I0516 20:46:40.788352 8312 filt.go:46] [1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 106 60 0 0 10] 127 | I0516 20:46:40.788865 8312 serial.go:224] left buff: [] 128 | I0516 20:46:40.788919 8312 serial.go:225] got vars: [{1 8 556998961 15466 [0 0 0 0 0 0 0 0]}] 129 | I0516 20:46:40.801214 8312 serial.go:177] GrReceive: default... 130 | I0516 20:46:40.801416 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 122 60 0 0 10 192] 131 | I0516 20:46:40.801995 8312 serial.go:184] GrReceive: send chRx... 132 | I0516 20:46:40.817207 8312 serial.go:177] GrReceive: default... 133 | I0516 20:46:40.817767 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 138 60 0 0 10 192] 134 | I0516 20:46:40.818420 8312 serial.go:184] GrReceive: send chRx... 135 | I0516 20:46:40.832520 8312 serial.go:177] GrReceive: default... 136 | I0516 20:46:40.833682 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 154 60 0 0 10 192] 137 | I0516 20:46:40.834274 8312 serial.go:184] GrReceive: send chRx... 138 | I0516 20:46:40.847854 8312 serial.go:177] GrReceive: default... 139 | I0516 20:46:40.848245 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 169 60 0 0 10 192] 140 | I0516 20:46:40.849783 8312 serial.go:184] GrReceive: send chRx... 141 | I0516 20:46:40.875660 8312 serial.go:177] GrReceive: default... 142 | I0516 20:46:40.896117 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 217 60 0 0 10 192] 143 | I0516 20:46:40.903626 8312 serial.go:184] GrReceive: send chRx... 144 | I0516 20:46:40.925364 8312 serial.go:177] GrReceive: default... 145 | I0516 20:46:40.925466 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 246 60 0 0 10 192] 146 | I0516 20:46:40.925466 8312 serial.go:184] GrReceive: send chRx... 147 | I0516 20:46:40.941014 8312 serial.go:177] GrReceive: default... 148 | I0516 20:46:40.942537 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 6 61 0 0 10 192] 149 | I0516 20:46:40.943305 8312 serial.go:184] GrReceive: send chRx... 150 | I0516 20:46:40.956934 8312 serial.go:177] GrReceive: default... 151 | I0516 20:46:40.957615 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 22 61 0 0 10 192] 152 | I0516 20:46:40.957615 8312 serial.go:184] GrReceive: send chRx... 153 | I0516 20:46:40.972801 8312 serial.go:177] GrReceive: default... 154 | I0516 20:46:40.972979 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 38 61 0 0 10 192] 155 | I0516 20:46:40.973490 8312 serial.go:184] GrReceive: send chRx... 156 | I0516 20:46:40.988042 8312 serial.go:177] GrReceive: default... 157 | I0516 20:46:40.988177 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 53 61 0 0 10 192] 158 | I0516 20:46:40.988687 8312 serial.go:184] GrReceive: send chRx... 159 | I0516 20:46:41.003248 8312 serial.go:177] GrReceive: default... 160 | I0516 20:46:41.004526 8312 serial.go:182] GrReceive b: [192 1 2 8 49 33 51 33 0 0 0 0 0 0 0 0 69 61 0 0 10 192] 161 | I0516 20:46:48.627657 8312 server.go:52] [::1]:59751 POST /variable_read 162 | I0516 20:46:48.630326 8312 jsonfile.go:19] C:\Users\bige\AppData\Roaming/asuwave/vToRead.json save success. 163 | I0516 20:46:48.646726 8312 server.go:52] [::1]:59751 GET /variable_read 164 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | 9 | "github.com/scutrobotlab/asuwave/internal/helper" 10 | "github.com/scutrobotlab/asuwave/internal/option" 11 | "github.com/scutrobotlab/asuwave/internal/serial" 12 | "github.com/scutrobotlab/asuwave/internal/server" 13 | "github.com/scutrobotlab/asuwave/pkg/elffile" 14 | ) 15 | 16 | func main() { 17 | vFlag := false 18 | uFlag := false 19 | bFlag := false 20 | flag.BoolVar(&vFlag, "i", false, "show version") 21 | flag.BoolVar(&uFlag, "u", false, "check update") 22 | flag.BoolVar(&bFlag, "b", true, "start browser") 23 | flag.IntVar(&helper.Port, "p", 8000, "port to bind") 24 | flag.Parse() 25 | 26 | if vFlag { 27 | fmt.Println(helper.GetVersion()) 28 | os.Exit(0) 29 | } 30 | 31 | if uFlag { 32 | helper.CheckUpdate(false) 33 | os.Exit(0) 34 | } 35 | 36 | option.Load() 37 | 38 | if val, ok := os.LookupEnv("PORT"); ok { 39 | helper.Port, _ = strconv.Atoi(val) 40 | } 41 | 42 | fsys := getFS() 43 | 44 | if bFlag { 45 | helper.StartBrowser("http://localhost:" + strconv.Itoa(helper.Port)) 46 | } 47 | 48 | go serial.GrReceive() 49 | go serial.GrTransmit() 50 | go serial.GrRxPrase() 51 | go elffile.FileWatch() 52 | server.Start(&fsys) 53 | } 54 | -------------------------------------------------------------------------------- /main_dev.go: -------------------------------------------------------------------------------- 1 | //go:build !release 2 | 3 | package main 4 | 5 | import ( 6 | "io/fs" 7 | "os" 8 | ) 9 | 10 | func getFS() fs.FS { 11 | return os.DirFS("dist") 12 | } 13 | -------------------------------------------------------------------------------- /main_release.go: -------------------------------------------------------------------------------- 1 | //go:build release 2 | 3 | package main 4 | 5 | import ( 6 | "embed" 7 | "io/fs" 8 | ) 9 | 10 | // content is our web server content. 11 | //go:embed dist 12 | var content embed.FS 13 | 14 | func getFS() fs.FS { 15 | fsys, err := fs.Sub(content, "dist") 16 | if err != nil { 17 | panic(err) 18 | } 19 | return fsys 20 | } 21 | -------------------------------------------------------------------------------- /mcu/SerialLineIP.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SerialLineIP.hpp 3 | * @author BigeYoung (SCUT.BigeYoung@gmail.com) 4 | * @brief SerialLineIP 是一种简单的数据链路层串口协议, 5 | * 提供了封装成帧和透明传输的功能。 6 | * @warning STANDARD C++03 REQUIRED! 需要C++03以上标准支持! 7 | * - C++03起,vector 元素相继存储,因此,您能用指向元素的常规指针 8 | * 访问元素。例如,对于返回值data,您可以使用&data[0]获取数组首地址。 9 | * [C++标准:vector](https://zh.cppreference.com/w/cpp/container/vector) 10 | * - 依照 ISO C++ 标准建议,本程序的函数直接返回 vector 容器。 11 | * 在支持具名返回值优化(NRVO)的编译器上可获得最佳性能。 12 | * [C++标准:复制消除](https://zh.cppreference.com/w/cpp/language/copy_elision) 13 | * @see [RFC-1055: SLIP 协议文档](https://tools.ietf.org/html/rfc1055) 14 | * @version 0.1 15 | * @date 2018-12-24 16 | * 17 | * @copyright Copyright 华工机器人实验室(c) 2018 18 | * 19 | */ 20 | #include 21 | #include /* uint8_t */ 22 | #include /* assert */ 23 | #include /* vector */ 24 | #include "SerialLineIP.hpp" 25 | using namespace SerialLineIP; 26 | 27 | /** 28 | * @brief Serial Line IP PACK 29 | * @param p_PDU 起始位置指针 30 | * @param PDU_len PDU 字节长度 31 | * @return std::vector SDU 32 | * @note Service Data Unit (SDU) 指本层封包后产生的数据单元 33 | * Protocol Data Unit (PDU) 指上层协议数据单元 34 | */ 35 | std::vector SerialLineIP::Pack(const void *const p_PDU, int PDU_len) 36 | { 37 | uint8_t *p_PDU_data = (uint8_t *)p_PDU; 38 | /* 计算所需内存空间 39 | */ 40 | int SDU_len = PDU_len + 2; //添加首尾2个END 41 | for (uint8_t *p = p_PDU_data; p < p_PDU_data + PDU_len; p++) 42 | { 43 | if ((*p == END) || (*p == ESC)) 44 | { 45 | SDU_len++; 46 | } 47 | } 48 | 49 | std::vector SDU_data; 50 | SDU_data.reserve(SDU_len); 51 | 52 | /* send an initial END character to flush out any data that may 53 | * have accumulated in the receiver due to line noise 54 | */ 55 | SDU_data.push_back(END); 56 | 57 | for (uint8_t *p = p_PDU_data; p < p_PDU_data + PDU_len; p++) 58 | { 59 | switch (*p) 60 | { 61 | /* if it's the same code as an END character, we send a 62 | * special two character code so as not to make the 63 | * receiver think we sent an END 64 | */ 65 | case END: 66 | SDU_data.push_back(ESC); 67 | SDU_data.push_back(ESC_END); 68 | break; 69 | 70 | /* if it's the same code as an ESC character, 71 | * we send a special two character code so as not 72 | * to make the receiver think we sent an ESC 73 | */ 74 | case ESC: 75 | SDU_data.push_back(ESC); 76 | SDU_data.push_back(ESC_ESC); 77 | break; 78 | 79 | /* otherwise, we just send the character 80 | */ 81 | default: 82 | SDU_data.push_back(*p); 83 | } 84 | } 85 | 86 | /* tell the receiver that we're done sending the packet 87 | */ 88 | SDU_data.push_back(END); 89 | 90 | /* 确认计算所需内存空间与实际内存空间一致 91 | */ 92 | assert(SDU_data.size() == SDU_len); 93 | 94 | return SDU_data; 95 | } 96 | 97 | /** 98 | * @brief Serial Line IP UNPACK 99 | * @param p_SDU 起始位置指针 100 | * @param SDU_len SDU 字节长度 101 | * @return std::vector PDU 102 | * @note Service Data Unit (SDU) 指本层解包前的数据单元 103 | * Protocol Data Unit (PDU) 指上层协议数据单元 104 | */ 105 | std::vector SerialLineIP::Unpack(const void *const p_SDU, int SDU_len) 106 | { 107 | std::vector PDU_data; 108 | PDU_data.reserve(SDU_len - 2); //分配足够多的内存空间,减去首位两个END 109 | bool begin = false; 110 | 111 | uint8_t *p_SDU_data = (uint8_t *)p_SDU; 112 | for (uint8_t *p = p_SDU_data; p < p_SDU_data + SDU_len; p++) 113 | { 114 | /* skip until first END character found 115 | */ 116 | if (!begin) 117 | { 118 | if (*p != END) 119 | continue; 120 | else 121 | begin = true; 122 | } 123 | 124 | /* handle bytestuffing if necessary 125 | */ 126 | switch (*p) 127 | { 128 | /* if it's an END character then we're done with 129 | * the packet 130 | */ 131 | case END: 132 | /* a minor optimization: if there is no 133 | * data in the packet, ignore it. This is 134 | * meant to avoid bothering IP with all 135 | * the empty packets generated by the 136 | * duplicate END characters which are in 137 | * turn sent to try to detect line noise. 138 | */ 139 | if (PDU_data.size() > 0) 140 | return PDU_data; 141 | else 142 | break; 143 | 144 | /* if it's the same code as an ESC character, wait 145 | * and get another character and then figure out 146 | * what to store in the packet based on that. 147 | */ 148 | case ESC: 149 | p++; 150 | 151 | /* if "p" is not one of these two, then we 152 | * have a protocol violation. The best bet 153 | * seems to be to leave the byte alone and 154 | * just stuff it into the packet 155 | */ 156 | switch (*p) 157 | { 158 | case ESC_END: 159 | PDU_data.push_back(END); 160 | break; 161 | case ESC_ESC: 162 | PDU_data.push_back(ESC); 163 | break; 164 | default: 165 | /* ESC 后面跟了其他字符 166 | * 这在协议中没有定义, 167 | * 则认为这一帧出现差错。 168 | */ 169 | begin = false; 170 | PDU_data.clear(); 171 | } 172 | break; 173 | 174 | /* here we fall into the default handler and let 175 | * it store the character for us 176 | */ 177 | default: 178 | PDU_data.push_back(*p); 179 | } 180 | } 181 | /* 如果没有结束'END'标识符,则返回空 182 | */ 183 | PDU_data.clear(); 184 | return PDU_data; 185 | } -------------------------------------------------------------------------------- /mcu/SerialLineIP.hpp: -------------------------------------------------------------------------------- 1 | /// UTF-8 Encode 2 | /** 3 | * @file SerialLineIP.hpp 4 | * @author BigeYoung (SCUT.BigeYoung@gmail.com) 5 | * @brief SerialLineIP 是一种简单的数据链路层串口协议, 6 | * 提供了封装成帧和透明传输的功能。 7 | * @warning STANDARD C++03 REQUIRED! 需要C++03以上标准支持! 8 | * - C++03起,vector 元素相继存储,因此,您能用指向元素的常规指针 9 | * 访问元素。例如,对于返回值data,您可以使用&data[0]获取数组首地址。 10 | * [C++标准:vector](https://zh.cppreference.com/w/cpp/container/vector) 11 | * - 依照 ISO C++ 标准建议,本程序的函数直接返回 vector 容器。 12 | * 在支持具名返回值优化(NRVO)的编译器上可获得最佳性能。 13 | * [C++标准:复制消除](https://zh.cppreference.com/w/cpp/language/copy_elision) 14 | * @see [RFC-1055: SLIP 协议文档](https://tools.ietf.org/html/rfc1055) 15 | * @version 0.1 16 | * @date 2018-12-24 17 | * 18 | * @copyright Copyright 华工机器人实验室(c) 2018 19 | * 20 | */ 21 | #ifndef SERIAL_LINE_IP_H 22 | #define SERIAL_LINE_IP_H 23 | #include 24 | #include /* uint8_t */ 25 | #include /* assert */ 26 | #include /* vector */ 27 | namespace SerialLineIP 28 | { 29 | /* SLIP special character codes */ 30 | const uint8_t END = 0xC0; /* indicates end of packet */ 31 | const uint8_t ESC = 0xDB; /* indicates byte stuffing */ 32 | const uint8_t ESC_END = 0xDC; /* ESC ESC_END means END data byte */ 33 | const uint8_t ESC_ESC = 0xDD; /* ESC ESC_ESC means ESC data byte */ 34 | 35 | /** 36 | * @brief Serial Line IP PACK 37 | * @param p_PDU 起始位置指针 38 | * @param PDU_len PDU 字节长度 39 | * @return std::vector SDU 40 | * @note Service Data Unit (SDU) 指本层封包后产生的数据单元 41 | * Protocol Data Unit (PDU) 指上层协议数据单元 42 | */ 43 | std::vector Pack(const void *const p_PDU, int PDU_len); 44 | 45 | /** 46 | * @brief Serial Line IP UNPACK 47 | * @param p_SDU 起始位置指针 48 | * @param SDU_len SDU 字节长度 49 | * @return std::vector PDU 50 | * @note Service Data Unit (SDU) 指本层解包前的数据单元 51 | * Protocol Data Unit (PDU) 指上层协议数据单元 52 | */ 53 | std::vector Unpack(const void *const p_SDU, int SDU_len); 54 | } // namespace SerialLineIP 55 | #endif // SERIAL_LINE_IP_H -------------------------------------------------------------------------------- /mcu/asuwave.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | ****************************************************************************** 3 | * @file asuwave.c 4 | * @author M3chD09 rinngo17@foxmail.com 5 | * @brief 6 | * @version 1.0 7 | * @date 6th Apr 2021 8 | ****************************************************************************** 9 | ============================================================================== 10 | How to use this Utility 11 | ============================================================================== 12 | [..] 13 | Initialize the asuwave using asuwave_init() function: 14 | Specify the UART to communicate with computer. 15 | Specify the function to obtain system tick, 16 | which can be xTaskGetTickCount if using FreeRTOS. 17 | [..] 18 | Register asuwave_callback() in UART callback function. 19 | [..] 20 | Call asuwave_subscribe() in a FreeRTOS task: 21 | A delay of 10ms is OK. 22 | ****************************************************************************** 23 | */ 24 | 25 | 26 | /* Includes ------------------------------------------------------------------*/ 27 | #include "asuwave.hpp" 28 | #include "string.h" 29 | #include "SerialLineIP.hpp" 30 | 31 | /* Private variables ---------------------------------------------------------*/ 32 | typedef uint32_t (*getTick_f)(void); 33 | static getTick_f getTick; 34 | static UART_HandleTypeDef *huart_x; 35 | 36 | /** 37 | * @brief board ID definition 38 | */ 39 | enum ASUWAVE_BOARD 40 | { 41 | ASUWAVE_BOARD_1 = 0x01, 42 | ASUWAVE_BOARD_2, 43 | ASUWAVE_BOARD_3 44 | }; 45 | 46 | /** 47 | * @brief action information definition 48 | */ 49 | enum ASUWAVE_ACT 50 | { 51 | ASUWAVE_ACT_SUBSCRIBE = 0x01, 52 | ASUWAVE_ACT_SUBSCRIBERETURN, 53 | ASUWAVE_ACT_UNSUBSCRIBE, 54 | ASUWAVE_ACT_UNSUBSCRIBERETURN, 55 | ASUWAVE_ACT_READ, 56 | ASUWAVE_ACT_READRETURN, 57 | ASUWAVE_ACT_WRITE, 58 | ASUWAVE_ACT_WRITERETURN 59 | }; 60 | 61 | /** 62 | * @brief error information definition 63 | */ 64 | enum ASUWAVE_ERROR 65 | { 66 | ASUWAVE_ERROR_NOSUCHADDRREG = 0xf9, 67 | ASUWAVE_ERROR_FULLADDR, 68 | ASUWAVE_ERROR_NOSUCHDATANUM, 69 | ASUWAVE_ERROR_NOSUCHADDR, 70 | ASUWAVE_ERROR_NOSUCHACT, 71 | ASUWAVE_ERROR_NOSUCHBOARD 72 | }; 73 | 74 | /** 75 | * @brief asuwave data to receive structure definition 76 | */ 77 | typedef struct 78 | { 79 | uint8_t board :8; /*!< The ID of the board to be operated. 80 | This parameter can be any value of @ref ASUWAVE_BOARD */ 81 | 82 | uint8_t act :8; /*!< The action to be performed or the error information to be sent. 83 | This parameter can be any value of @ref ASUWAVE_ACT or @ref ASUWAVE_ERROR */ 84 | 85 | uint8_t dataNum :8; /*!< Size amount of data to be operated. 86 | This parameter must be a number between Min_Data = 0 and Max_Data = 8. */ 87 | 88 | uint32_t addr :32; /*!< The address of MCU to be operated. 89 | This parameter must be a number between Min_Data = 0x20000000 and Max_Data = 0x80000000. */ 90 | 91 | uint64_t dataBuf :64; /*!< The data read or to be written. 92 | This parameter must be a variable of type uint64_t */ 93 | 94 | uint8_t carriageReturn :8; /*!< The carriage return. 95 | This parameter must be '\n' */ 96 | 97 | } __attribute__((packed)) asuwave_rx_t; 98 | 99 | /** 100 | * @brief asuwave data to transmit structure definition 101 | */ 102 | typedef struct 103 | { 104 | uint8_t board :8; /*!< The ID of the board to be operated. 105 | This parameter can be any value of @ref ASUWAVE_BOARD */ 106 | 107 | uint8_t act :8; /*!< The action to be performed or the error information to be sent. 108 | This parameter can be any value of @ref ASUWAVE_ACT or @ref ASUWAVE_ERROR */ 109 | 110 | uint8_t dataNum :8; /*!< Size amount of data to be operated. 111 | This parameter must be a number between Min_Data = 0 and Max_Data = 8. */ 112 | 113 | uint32_t addr :32; /*!< The address of MCU to be operated. 114 | This parameter must be a number between Min_Data = 0x20000000 and Max_Data = 0x80000000. */ 115 | 116 | uint64_t dataBuf :64; /*!< The data read or to be written. 117 | This parameter must be a variable of type uint64_t */ 118 | 119 | uint32_t tick :32; /*!< The tick count. 120 | This parameter must be a variable of type uint32_t */ 121 | 122 | uint8_t carriageReturn :8; /*!< The carriage return. 123 | This parameter must be '\n' */ 124 | 125 | } __attribute__((packed)) asuwave_tx_t; 126 | 127 | /** 128 | * @brief asuwave data to receive union definition 129 | */ 130 | typedef union 131 | { 132 | asuwave_rx_t body; 133 | uint8_t buff[16]; 134 | } asuwave_rxu_t; 135 | 136 | /** 137 | * @brief asuwave data to transmit union definition 138 | */ 139 | typedef union 140 | { 141 | asuwave_tx_t body; 142 | uint8_t buff[20]; 143 | } asuwave_txu_t; 144 | 145 | /* definition of the list of address to read */ 146 | #define MAX_ADDR_NUM 10 147 | typedef struct 148 | { 149 | uint8_t dataNum; 150 | uint32_t addr; 151 | } list_addr_t; 152 | static list_addr_t list_addr[MAX_ADDR_NUM]; 153 | 154 | /* function prototypes -------------------------------------------------------*/ 155 | 156 | /** 157 | * @brief Register the address. 158 | * @param asuwave_rxu: received asuwave data union. 159 | * @retval the index of the list_addr to register. 160 | */ 161 | int8_t addr_register(asuwave_rxu_t *asuwave_rxu) 162 | { 163 | for (int i = 0; i < MAX_ADDR_NUM; i++) 164 | { 165 | /* Find the index of list_addr that is null */ 166 | if (list_addr[i].dataNum == 0 167 | || list_addr[i].addr == asuwave_rxu->body.addr) 168 | { 169 | list_addr[i].dataNum = asuwave_rxu->body.dataNum; 170 | list_addr[i].addr = asuwave_rxu->body.addr; 171 | return i; 172 | } 173 | } 174 | return -1; 175 | } 176 | 177 | /** 178 | * @brief Unregister the address. 179 | * @param asuwave_rxu: received asuwave data union. 180 | * @retval the index of the list_addr to unregister. 181 | */ 182 | int8_t addr_unregister(asuwave_rxu_t *asuwave_rxu) 183 | { 184 | for (int i = 0; i < MAX_ADDR_NUM; i++) 185 | { 186 | /* Find the index of list_addr */ 187 | if (list_addr[i].addr == asuwave_rxu->body.addr) 188 | { 189 | list_addr[i].dataNum = 0; 190 | list_addr[i].addr = 0; 191 | return i; 192 | } 193 | } 194 | return -1; 195 | } 196 | 197 | /** 198 | * @brief Send an error message via uart. 199 | * @param err: the error information. 200 | * @param asuwave_rxu: received asuwave data union. 201 | * @retval None 202 | */ 203 | void return_err(asuwave_rxu_t *asuwave_rxu, uint8_t err) 204 | { 205 | /* Clear the data buffer to be sent */ 206 | asuwave_txu_t asuwave_txu; 207 | memset((uint8_t*) &asuwave_txu.buff, 0, 208 | sizeof(asuwave_txu.buff)); 209 | 210 | /* Prepare the data to send */ 211 | asuwave_txu.body.board = asuwave_rxu->body.board; 212 | asuwave_txu.body.act = err; 213 | asuwave_txu.body.addr = asuwave_rxu->body.addr; 214 | asuwave_txu.body.dataNum = asuwave_rxu->body.dataNum; 215 | asuwave_txu.body.carriageReturn = '\n'; 216 | 217 | /* Send the error message */ 218 | HAL_UART_Transmit_DMA(huart_x, asuwave_txu.buff, 9); 219 | } 220 | 221 | /** 222 | * @brief Subscribes the variable in flash memory of the given address. 223 | * @param None 224 | * @retval None 225 | */ 226 | void asuwave_subscribe(void) 227 | { 228 | /* definition of the data pack and length to send */ 229 | std::vector tx_buff; 230 | static uint16_t tx_len = 0; 231 | static asuwave_txu_t asuwave_txu; 232 | 233 | /* Clear the data buffer to be sent */ 234 | memset((uint8_t*) &asuwave_txu.buff, 0, 235 | sizeof(asuwave_txu.buff)); 236 | tx_len = 0; 237 | 238 | /* Prepare the data to send */ 239 | asuwave_txu.body.board = ASUWAVE_BOARD_1; 240 | asuwave_txu.body.act = ASUWAVE_ACT_SUBSCRIBERETURN; 241 | asuwave_txu.body.carriageReturn = '\n'; 242 | 243 | uint16_t dataNum; 244 | uint32_t addr; 245 | for (int i = 0; i < MAX_ADDR_NUM; i++) 246 | { 247 | if (list_addr[i].dataNum != 0) 248 | { 249 | addr = list_addr[i].addr; 250 | asuwave_txu.body.addr = list_addr[i].addr; 251 | /* Reads the variable in flash memory */ 252 | for (dataNum = 0; dataNum < list_addr[i].dataNum; dataNum++) 253 | { 254 | *(asuwave_txu.buff + 7 + dataNum) = *(__IO uint8_t*) addr++; 255 | } 256 | asuwave_txu.body.dataNum = dataNum; 257 | asuwave_txu.body.tick = getTick(); 258 | /* Pack using SLIP */ 259 | std::vector packet = SerialLineIP::Pack(asuwave_txu.buff, 20); 260 | tx_buff.insert(packet.begin(), packet.end()); 261 | } 262 | } 263 | 264 | /* Send return data */ 265 | HAL_UART_Transmit_DMA(huart_x, &tx_buff[0], tx_buff.size()); 266 | } 267 | 268 | /** 269 | * @brief Writes the given data buffer to the flash of the given address. 270 | * @param asuwave_rxu: received asuwave data union. 271 | * @retval None 272 | */ 273 | void write_flash(asuwave_rxu_t *asuwave_rxu) 274 | { 275 | /* Clear the data buffer to be sent */ 276 | asuwave_txu_t asuwave_txu; 277 | memset((uint8_t*) &asuwave_txu.buff, 0, 278 | sizeof(asuwave_txu.buff)); 279 | 280 | /* Prepare the data to send */ 281 | asuwave_txu.body.board = asuwave_rxu->body.board; 282 | asuwave_txu.body.addr = asuwave_rxu->body.addr; 283 | asuwave_txu.body.dataNum = asuwave_rxu->body.dataNum; 284 | asuwave_txu.body.carriageReturn = '\n'; 285 | 286 | /* Write data buffer */ 287 | uint32_t TypeProgram = 0; 288 | uint8_t n = asuwave_rxu->body.dataNum; 289 | if (n > 8 || (n & (n - 1))) 290 | { 291 | return_err(asuwave_rxu, ASUWAVE_ERROR_NOSUCHDATANUM); 292 | return; 293 | } 294 | while (n >>= 1) TypeProgram++; 295 | 296 | if (HAL_FLASH_Unlock() == HAL_OK) 297 | if (HAL_FLASH_Program(TypeProgram, asuwave_rxu->body.addr, 298 | asuwave_rxu->body.dataBuf) == HAL_OK) 299 | if (HAL_FLASH_Lock() == HAL_OK) 300 | asuwave_txu.body.act = ASUWAVE_ACT_WRITERETURN; 301 | 302 | /* Send return data */ 303 | HAL_UART_Transmit_DMA(huart_x, asuwave_txu.buff, 8); 304 | } 305 | 306 | /** 307 | * @brief asuwave callback. 308 | * @param data_buf: received buffer array. 309 | * @param length: the length of array. 310 | * @retval None 311 | */ 312 | uint32_t asuwave_callback(uint8_t *data_buf, uint16_t length) 313 | { 314 | asuwave_rxu_t asuwave_rxu; 315 | if (length != sizeof(asuwave_rxu.buff)) return 1; 316 | memcpy(&asuwave_rxu.buff, data_buf, length); 317 | 318 | /* Check if it is a valid board ID */ 319 | if (asuwave_rxu.body.board == ASUWAVE_BOARD_1) 320 | { 321 | 322 | /* Check if it is a valid action information and execute */ 323 | switch (asuwave_rxu.body.act) 324 | { 325 | case ASUWAVE_ACT_SUBSCRIBE: 326 | if (addr_register(&asuwave_rxu) == -1) 327 | return_err(&asuwave_rxu, ASUWAVE_ERROR_FULLADDR); 328 | break; 329 | case ASUWAVE_ACT_WRITE: 330 | write_flash(&asuwave_rxu); 331 | break; 332 | case ASUWAVE_ACT_UNSUBSCRIBE: 333 | if (addr_unregister(&asuwave_rxu) == -1) 334 | return_err(&asuwave_rxu, ASUWAVE_ERROR_NOSUCHADDRREG); 335 | break; 336 | default: 337 | return_err(&asuwave_rxu, ASUWAVE_ERROR_NOSUCHACT); 338 | return 1; 339 | } 340 | 341 | } 342 | else if (asuwave_rxu.body.board == ASUWAVE_BOARD_2 343 | || asuwave_rxu.body.board == ASUWAVE_BOARD_3) 344 | { 345 | //TODO 346 | } 347 | else 348 | { 349 | return_err(&asuwave_rxu, ASUWAVE_ERROR_NOSUCHBOARD); 350 | } 351 | return 0; 352 | } 353 | 354 | /** 355 | * @brief Register uart device. 356 | * @param *huart: pointer of uart IRQHandler. 357 | * @param *f: function pointer to obtain system tick, which can be xTaskGetTickCount if using FreeRTOS 358 | * @retval None 359 | */ 360 | void asuwave_init(UART_HandleTypeDef *huart, uint32_t (*f)(void)) 361 | { 362 | huart_x = huart; 363 | getTick = f; 364 | } 365 | 366 | -------------------------------------------------------------------------------- /mcu/asuwave.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | ****************************************************************************** 3 | * @file asuwave.h 4 | * @author M3chD09 rinngo17@foxmail.com 5 | * @brief Header file of asuwave.c 6 | * @version 1.0 7 | * @date 6th Apr 2021 8 | ****************************************************************************** 9 | 10 | ****************************************************************************** 11 | */ 12 | 13 | /* Define to prevent recursive inclusion -------------------------------------*/ 14 | #ifndef _ASUWAVE_H_ 15 | #define _ASUWAVE_H_ 16 | 17 | /* Includes ------------------------------------------------------------------*/ 18 | #if defined(USE_HAL_DRIVER) 19 | #if defined(STM32F405xx) || defined(STM32F407xx) 20 | #include 21 | #endif 22 | #if defined(STM32F103xx) 23 | #include 24 | #endif 25 | #if defined(STM32H750xx) 26 | #include 27 | #endif 28 | #endif 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | /* Exported function declarations --------------------------------------------*/ 35 | void asuwave_init(UART_HandleTypeDef *huart, uint32_t (*f)(void)); 36 | uint32_t asuwave_callback(uint8_t *data_buf, uint16_t length); 37 | void asuwave_subscribe(void); 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | 43 | #endif /* _ASUWAVE_H_ */ 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asuwave", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "format": "prettier --write src/**/*.{js,vue}", 10 | "lint:fix": "eslint --fix src/**/*.{js,vue}" 11 | }, 12 | "dependencies": { 13 | "@fontsource/roboto": "^4.5.5", 14 | "@mdi/font": "^6.6.96", 15 | "core-js": "^3.22.2", 16 | "timechart": "^1.0.0-beta.10", 17 | "vue": "^2.6.11", 18 | "vuetify": "2.7.1", 19 | "vuex": "^3.4.0" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "^5.0.4", 23 | "@vue/cli-plugin-eslint": "^5.0.4", 24 | "@vue/cli-plugin-vuex": "^5.0.4", 25 | "@vue/cli-service": "^5.0.4", 26 | "babel-eslint": "^10.1.0", 27 | "eslint": "^7.27.0", 28 | "eslint-plugin-vue": "^7.20.0", 29 | "sass": "1.32.13", 30 | "sass-loader": "^10.2.1", 31 | "vue-cli-plugin-vuetify": "^2.4.8", 32 | "vue-template-compiler": "^2.6.11", 33 | "vuetify-loader": "^1.7.0" 34 | }, 35 | "eslintConfig": { 36 | "root": true, 37 | "env": { 38 | "node": true 39 | }, 40 | "extends": [ 41 | "plugin:vue/essential", 42 | "eslint:recommended" 43 | ], 44 | "parserOptions": { 45 | "parser": "babel-eslint" 46 | }, 47 | "rules": {} 48 | }, 49 | "browserslist": [ 50 | "> 1%", 51 | "last 2 versions", 52 | "not dead" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /pic/IMG_0095.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scutrobotlab/asuwave/dbe0b8d43200bf2a322fb887b1f8f7460b4d9f07/pic/IMG_0095.png -------------------------------------------------------------------------------- /pic/IMG_0096.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scutrobotlab/asuwave/dbe0b8d43200bf2a322fb887b1f8f7460b4d9f07/pic/IMG_0096.png -------------------------------------------------------------------------------- /pkg/elffile/elffile.go: -------------------------------------------------------------------------------- 1 | /* 2 | 我的秘密,只对你一人说 3 | */ 4 | package elffile 5 | 6 | import ( 7 | "debug/dwarf" 8 | "debug/elf" 9 | "errors" 10 | "fmt" 11 | "os" 12 | "strconv" 13 | "strings" 14 | 15 | "github.com/scutrobotlab/asuwave/internal/variable" 16 | ) 17 | 18 | // 你,真的是我一直在等的人吗 19 | func Check(f *os.File) (*elf.File, error) { 20 | var ident [4]byte 21 | f.ReadAt(ident[:], 0) 22 | 23 | // 头上戴着你特有的饰品 24 | if string(ident[:]) != elf.ELFMAG { 25 | return nil, errors.New("bad magic number") 26 | } 27 | 28 | // 带着礼物而来 29 | eFile, err := elf.NewFile(f) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | // 此生无缘,转身离去 35 | if eFile.FileHeader.Class != elf.ELFCLASS32 || eFile.FileHeader.Machine != elf.EM_ARM { 36 | return nil, errors.New("not valid ELF") 37 | } 38 | 39 | // 若是有缘,三生有幸 40 | return eFile, nil 41 | } 42 | 43 | // 将你心中的秘密,分享给我吧 44 | func ReadVariable(f *elf.File) (variable.Projs, error) { 45 | 46 | // 排除一切杂念,听你娓娓道来 47 | x := variable.Projs{} 48 | 49 | // 故事很长,从那一天说起吧 50 | dwarfData, err := f.DWARF() 51 | if err != nil { 52 | return x, err 53 | } 54 | r := dwarfData.Reader() 55 | 56 | for { 57 | 58 | // 不断倾诉你心底的声音 59 | entry, err := r.Next() 60 | if err != nil { 61 | return x, err 62 | } 63 | 64 | // 直到尽头才满意地离开 65 | if entry == nil { 66 | return x, nil 67 | } 68 | 69 | // 重要的回忆,怎能忘记 70 | if entry.Tag == dwarf.TagVariable && !entry.Children { 71 | y := variable.ProjT{} 72 | var a uint32 73 | 74 | // 探访你的住址 75 | if v, ok := entry.Val(dwarf.AttrLocation).([]byte); ok { 76 | if len(v) != 5 { 77 | continue 78 | } 79 | a = variable.BytesToUint32(v[1:]) 80 | 81 | // 早已远去的人,只能放弃 82 | if a < 0x20000000 || a >= 0x80000000 { 83 | continue 84 | } 85 | 86 | y.Addr = fmt.Sprintf("0x%08x", a) 87 | } 88 | 89 | // 呼喊着你的名字 90 | if v, ok := entry.Val(dwarf.AttrName).(string); ok { 91 | y.Name = v 92 | } 93 | 94 | // 尝试读懂你的心 95 | if v, ok := entry.Val(dwarf.AttrType).(dwarf.Offset); ok { 96 | t, err := dwarfData.Type(v) 97 | 98 | if err != nil { 99 | continue 100 | } 101 | 102 | // 有时你的心难以琢磨 103 | if s, ok := checkStruct(t); ok { 104 | 105 | // 尝试着一层一层地拨开 106 | namePrefix := []string{y.Name} 107 | dfsStruct(namePrefix, a, &x, s.Field) 108 | 109 | } else if ar, ok := checkArray(t); ok { 110 | namePrefix := []string{y.Name} 111 | dfsArray(namePrefix, a, &x, ar.Type, ar.Count) 112 | } else { 113 | 114 | // 别用谎言欺骗自己 115 | if _, ok := variable.TypeLen[t.String()]; !ok { 116 | continue 117 | } 118 | 119 | // 只想听听你真实的声音 120 | y.Type = t.String() 121 | } 122 | } 123 | 124 | // 那些小事,就让它消失在风里 125 | if y.Addr == "" || y.Name == "" || y.Type == "" { 126 | continue 127 | } 128 | 129 | // 却总有些事,难以忘记 130 | x[y.Name] = y 131 | } 132 | } 133 | } 134 | 135 | // 一层一层地拨开你的心 136 | func dfsStruct(namePrefix []string, addrPrefix uint32, x *variable.Projs, s []*dwarf.StructField) { 137 | 138 | // 不愿放过每一个问题 139 | for _, v := range s { 140 | 141 | // 尝试琢磨你的心 142 | if st, ok := checkStruct(v.Type); ok { 143 | 144 | // 回首曾经走过的路 145 | namePrefix = append(namePrefix, v.Name) 146 | addrPrefix = addrPrefix + uint32(v.ByteOffset) 147 | 148 | // 勇敢地接着走下去 149 | dfsStruct(namePrefix, addrPrefix, x, st.Field) 150 | 151 | // 回到路口,准备下一次的旅程 152 | namePrefix = namePrefix[:len(namePrefix)-1] 153 | addrPrefix = addrPrefix - uint32(v.ByteOffset) 154 | 155 | } else if a, ok := checkArray(v.Type); ok { 156 | namePrefix = append(namePrefix, v.Name) 157 | addrPrefix = addrPrefix + uint32(v.ByteOffset) 158 | dfsArray(namePrefix, addrPrefix, x, a.Type, a.Count) 159 | namePrefix = namePrefix[:len(namePrefix)-1] 160 | addrPrefix = addrPrefix - uint32(v.ByteOffset) 161 | } else { 162 | 163 | // 终于,你缓缓开口 164 | a := addrPrefix + uint32(v.ByteOffset) 165 | if a < 0x20000000 || a >= 0x80000000 { 166 | continue 167 | } 168 | t := v.Type.String() 169 | if _, ok := variable.TypeLen[t]; !ok { 170 | continue 171 | } 172 | 173 | // 道出心底的秘密 174 | name := strings.Join(namePrefix, ".") + "." + v.Name 175 | (*x)[name] = variable.ProjT{ 176 | Name: name, 177 | Addr: fmt.Sprintf("0x%08x", a), 178 | Type: v.Type.String(), 179 | } 180 | } 181 | } 182 | } 183 | 184 | // 寂静来袭 185 | func checkStruct(t dwarf.Type) (*dwarf.StructType, bool) { 186 | 187 | // 说点什么吧 188 | if s, ok := t.(*dwarf.StructType); ok { 189 | return s, true 190 | } 191 | 192 | // 不愿就此放弃 193 | if td, ok := t.(*dwarf.TypedefType); ok { 194 | if s, ok := td.Type.(*dwarf.StructType); ok { 195 | return s, true 196 | } 197 | } 198 | 199 | // 最终仍是沉默 200 | return nil, false 201 | } 202 | 203 | func dfsArray(namePrefix []string, addrPrefix uint32, x *variable.Projs, t dwarf.Type, c int64) { 204 | 205 | for i := int64(0); i < c; i++ { 206 | if st, ok := checkStruct(t); ok { 207 | namePrefix = append(namePrefix, "["+strconv.FormatInt(i, 10)+"]") 208 | dfsStruct(namePrefix, addrPrefix, x, st.Field) 209 | namePrefix = namePrefix[:len(namePrefix)-1] 210 | } else if a, ok := checkArray(t); ok { 211 | namePrefix = append(namePrefix, "["+strconv.FormatInt(i, 10)+"]") 212 | dfsArray(namePrefix, addrPrefix, x, a.Type, a.Count) 213 | namePrefix = namePrefix[:len(namePrefix)-1] 214 | } else { 215 | a := addrPrefix 216 | if a < 0x20000000 || a >= 0x80000000 { 217 | continue 218 | } 219 | t := t.String() 220 | if _, ok := variable.TypeLen[t]; !ok { 221 | continue 222 | } 223 | 224 | name := strings.Join(namePrefix, ".") + ".[" + strconv.FormatInt(i, 10) + "]" 225 | (*x)[name] = variable.ProjT{ 226 | Name: name, 227 | Addr: fmt.Sprintf("0x%08x", a), 228 | Type: t, 229 | } 230 | } 231 | addrPrefix = addrPrefix + uint32(t.Size()) 232 | } 233 | } 234 | 235 | func checkArray(t dwarf.Type) (*dwarf.ArrayType, bool) { 236 | if a, ok := t.(*dwarf.ArrayType); ok { 237 | if a.Count != -1 { 238 | return a, true 239 | } 240 | } 241 | 242 | return nil, false 243 | } 244 | -------------------------------------------------------------------------------- /pkg/elffile/watch.go: -------------------------------------------------------------------------------- 1 | package elffile 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "github.com/fsnotify/fsnotify" 8 | "github.com/golang/glog" 9 | "github.com/scutrobotlab/asuwave/internal/variable" 10 | ) 11 | 12 | var watcher *fsnotify.Watcher 13 | var watchList []string 14 | 15 | var ChFileWrite chan string = make(chan string, 10) 16 | var ChFileError chan string = make(chan string, 10) 17 | var ChFileWatch chan string = make(chan string, 10) 18 | 19 | func GetWatchList() []string { 20 | glog.V(2).Infoln("Get: ", watchList) 21 | return watchList 22 | } 23 | 24 | func RemoveWathcer() error { 25 | l := watcher.WatchList() 26 | for _, p := range l { 27 | err := watcher.Remove(p) 28 | if err != nil { 29 | return err 30 | } 31 | } 32 | glog.V(2).Infoln("clear watcher") 33 | return nil 34 | } 35 | 36 | func FileWatch() { 37 | var err error 38 | watcher, err = fsnotify.NewWatcher() 39 | if err != nil { 40 | glog.Errorln(err.Error()) 41 | return 42 | } 43 | defer watcher.Close() 44 | watchdog := 0 45 | lastEventName := "" 46 | for { 47 | select { 48 | case file := <-ChFileWatch: 49 | for _, f := range watcher.WatchList() { 50 | watcher.Remove(f) 51 | } 52 | glog.Infoln("watch: ", file) 53 | watchList = []string{file} 54 | watcher.Add(file) 55 | case event, ok := <-watcher.Events: 56 | if !ok { 57 | glog.Warningln("Event not ok") 58 | return 59 | } 60 | glog.V(2).Infoln("file event:", event) 61 | if event.Op&fsnotify.Write == fsnotify.Write { 62 | lastEventName = event.Name 63 | watchdog = 0 64 | } 65 | case err, ok := <-watcher.Errors: 66 | if !ok { 67 | glog.Warningln("Error not ok") 68 | return 69 | } 70 | lastEventName = "" 71 | ChFileError <- err.Error() 72 | glog.Errorln("error:", err) 73 | case <-time.After(200 * time.Millisecond): 74 | if lastEventName != "" && watchdog < 5 { 75 | watchdog++ 76 | } else if lastEventName != "" && watchdog == 5 { 77 | glog.Infoln("file write done:", lastEventName) 78 | 79 | file, err := os.Open(lastEventName) 80 | if err != nil { 81 | glog.Errorln("file open:", err) 82 | return 83 | } 84 | 85 | f, err := Check(file) 86 | if err != nil { 87 | glog.Errorln("file check:", err) 88 | return 89 | } 90 | defer f.Close() 91 | 92 | projs, err := ReadVariable(f) 93 | if err != nil { 94 | glog.Errorln("file read:", err) 95 | return 96 | } 97 | variable.SetAllProj(projs) 98 | variable.UpdateByProj() 99 | ChFileWrite <- lastEventName 100 | watchdog++ 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /pkg/jsonfile/jsonfile.go: -------------------------------------------------------------------------------- 1 | package jsonfile 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | 7 | "github.com/golang/glog" 8 | ) 9 | 10 | func Save(filename string, v interface{}) { 11 | jsonTxt, err := json.Marshal(v) 12 | if err != nil { 13 | glog.Errorln(err.Error()) 14 | } 15 | err = os.WriteFile(filename, jsonTxt, 0644) 16 | if err != nil { 17 | glog.Errorln(err.Error()) 18 | } 19 | glog.Infoln(filename, "save success.") 20 | } 21 | 22 | func Load(filename string, v interface{}) { 23 | if _, err := os.Stat(filename); os.IsNotExist(err) { 24 | glog.Infoln(filename, " unfound.") 25 | return 26 | } 27 | glog.Infoln(filename, "Found") 28 | data, err := os.ReadFile(filename) 29 | if err != nil { 30 | glog.Errorln(err.Error()) 31 | } 32 | err = json.Unmarshal(data, v) 33 | if err != nil { 34 | glog.Errorln(err.Error()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/slip/slip.go: -------------------------------------------------------------------------------- 1 | /** 2 | * SerialLineIP 是一种简单的数据链路层串口协议,提供了封装成帧和透明传输的功能。 3 | * 请参阅:[RFC-1055: SLIP 协议文档](https://tools.ietf.org/html/rfc1055) 4 | **/ 5 | 6 | package slip 7 | 8 | import ( 9 | "errors" 10 | 11 | "github.com/golang/glog" 12 | ) 13 | 14 | /* SLIP special character codes */ 15 | const ( 16 | END byte = 0xC0 // indicates end of packet 17 | ESC byte = 0xDB // indicates byte stuffing 18 | ESC_END byte = 0xDC // ESC ESC_END means END data byte 19 | ESC_ESC byte = 0xDD // ESC ESC_ESC means ESC data byte 20 | ) 21 | 22 | /** 23 | * Serial Line IP PACK 24 | * Service Data Unit (SDU) 指本层封包后产生的数据单元 25 | * Protocol Data Unit (PDU) 指上层协议数据单元 26 | */ 27 | func Pack(PDU []byte) (SDU []byte) { 28 | SDU_len := len(PDU) + 2 29 | for _, p := range PDU { 30 | if p == END || p == ESC { 31 | SDU_len++ 32 | } 33 | } 34 | SDU = make([]byte, 0, SDU_len) 35 | 36 | SDU = append(SDU, END) 37 | for _, p := range PDU { 38 | switch p { 39 | case END: 40 | SDU = append(SDU, ESC, ESC_END) 41 | case ESC: 42 | SDU = append(SDU, ESC, ESC_ESC) 43 | default: 44 | SDU = append(SDU, p) 45 | } 46 | } 47 | SDU = append(SDU, END) 48 | if len(SDU) != SDU_len { 49 | glog.Errorf("SLIP: length %d != expected length %d", len(SDU), SDU_len) 50 | } 51 | return SDU 52 | } 53 | 54 | /** 55 | * Serial Line IP UNPACK 56 | * Service Data Unit (SDU) 指本层封包后产生的数据单元 57 | * Protocol Data Unit (PDU) 指上层协议数据单元 58 | */ 59 | func Unpack(SDU []byte) (PDU []byte, err error) { 60 | PDU = make([]byte, 0, len(SDU)-2) 61 | i := 0 62 | for ; i < len(SDU); i++ { 63 | if SDU[i] == END { 64 | break 65 | } 66 | } 67 | if SDU[i] != END { 68 | return []byte{}, errors.New("END unfound") 69 | } 70 | for i++; i < len(SDU); i++ { 71 | switch SDU[i] { 72 | case END: 73 | return PDU, nil 74 | case ESC: 75 | i++ 76 | switch SDU[i] { 77 | case ESC_END: 78 | PDU = append(PDU, END) 79 | case ESC_ESC: 80 | PDU = append(PDU, ESC) 81 | default: 82 | return PDU, errors.New("unknown byte after ESC") 83 | } 84 | default: 85 | PDU = append(PDU, SDU[i]) 86 | } 87 | } 88 | return []byte{}, errors.New("END unfound") 89 | } 90 | -------------------------------------------------------------------------------- /pkg/slip/slip_test.go: -------------------------------------------------------------------------------- 1 | package slip_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/scutrobotlab/asuwave/pkg/slip" 7 | ) 8 | 9 | func FuzzSlip(f *testing.F) { 10 | for s := 0; s < 40; s++ { 11 | seed := []byte{} 12 | for i := 0; i < s; i++ { 13 | if i%2 == 0 { 14 | seed = append(seed, slip.END) 15 | } 16 | if i%3 == 0 { 17 | seed = append(seed, slip.ESC) 18 | } 19 | if i%4 == 0 { 20 | seed = append(seed, slip.ESC_END) 21 | } 22 | if i%5 == 0 { 23 | seed = append(seed, slip.ESC_ESC) 24 | } 25 | } 26 | f.Add(seed) 27 | } 28 | f.Fuzz(func(t *testing.T, PDU []byte) { 29 | SDU := slip.Pack(PDU) 30 | 31 | if SDU[0] != slip.END { 32 | t.Fatal("SDU[0] not END, but: ", SDU[0]) 33 | } 34 | if SDU[len(SDU)-1] != slip.END { 35 | t.Fatal("SDU[-1] not END, but: ", SDU[len(SDU)-1]) 36 | } 37 | for i, s := range SDU { 38 | if i != 0 && i != len(SDU)-1 && s == slip.END { 39 | t.Fatalf("SDU[%d] got END", i) 40 | } 41 | } 42 | 43 | nPDU, err := slip.Unpack(SDU) 44 | if err != nil { 45 | t.Fatal(err.Error()) 46 | } 47 | 48 | t.Logf("PDU %v -> SDU %v -> nPDU %v", PDU, SDU, nPDU) 49 | 50 | if len(PDU) != len(nPDU) { 51 | t.Fatalf("PDU %v (len: %d) != nPDU %v (len: %d)", PDU, len(PDU), nPDU, len(nPDU)) 52 | } 53 | for i := range PDU { 54 | if PDU[i] != nPDU[i] { 55 | t.Fatalf("PDU[%d](%d) != nPDU[%d](%d)", i, PDU[i], i, nPDU[i]) 56 | } 57 | } 58 | t.Log("Success: ", PDU) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scutrobotlab/asuwave/dbe0b8d43200bf2a322fb887b1f8f7460b4d9f07/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 88 | -------------------------------------------------------------------------------- /src/api/file.js: -------------------------------------------------------------------------------- 1 | import { fetchApi, uploadApi } from "./internal"; 2 | 3 | export async function uploadFile(file) { 4 | return await uploadApi("/file/upload", "PUT", file); 5 | } 6 | 7 | export async function getFilePath() { 8 | const res = await fetchApi("/file/path", "GET"); 9 | return res; 10 | } 11 | 12 | export async function setFilePath(Path) { 13 | const res = await fetchApi("/file/path", "PUT", { Path }); 14 | return res; 15 | } 16 | 17 | export async function deleteFilePath() { 18 | const res = await fetchApi("/file/path", "DELETE"); 19 | return res; 20 | } 21 | -------------------------------------------------------------------------------- /src/api/internal.js: -------------------------------------------------------------------------------- 1 | export async function fetchApi(url, method = "GET", body = null) { 2 | let init = { 3 | method, 4 | headers: new Headers({ 5 | "Content-Type": "application/json", 6 | }), 7 | }; 8 | 9 | if (body != null && (method == "POST" || method == "PUT" || method == "DELETE")) { 10 | init.body = JSON.stringify(body); 11 | } 12 | 13 | const response = await fetch(url, init); 14 | 15 | if (response.status == 204) { 16 | return; 17 | } 18 | 19 | const text = await response.text(); 20 | let json = null; 21 | try { 22 | json = JSON.parse(text); 23 | } catch (e) { 24 | throw { status: response.status, data: text }; 25 | } 26 | 27 | if (response.ok) { 28 | return json; 29 | } 30 | throw { status: response.status, data: json.Error }; 31 | } 32 | 33 | export async function uploadApi(url, method, file) { 34 | const body = new FormData(); 35 | body.append("file", file); 36 | let init = { 37 | method, 38 | body, 39 | }; 40 | 41 | const response = await fetch(url, init); 42 | 43 | if (response.status == 204) { 44 | return; 45 | } 46 | 47 | const text = await response.text(); 48 | let json = null; 49 | try { 50 | json = JSON.parse(text); 51 | } catch (e) { 52 | throw { status: response.status, data: text }; 53 | } 54 | 55 | if (response.ok) { 56 | return json; 57 | } 58 | throw { status: response.status, data: json.Error }; 59 | } 60 | -------------------------------------------------------------------------------- /src/api/option.js: -------------------------------------------------------------------------------- 1 | import { fetchApi } from "./internal"; 2 | 3 | export async function getOption() { 4 | const res = await fetchApi("/option"); 5 | return res; 6 | } 7 | 8 | export async function setOption(Key, Value) { 9 | return await fetchApi("/option", "PUT", { Key, Value }); 10 | } 11 | -------------------------------------------------------------------------------- /src/api/serial.js: -------------------------------------------------------------------------------- 1 | import { fetchApi } from "./internal"; 2 | 3 | export async function getSerial() { 4 | const res = await fetchApi("/serial"); 5 | return res.Serials; 6 | } 7 | 8 | export async function getSerialCur() { 9 | const res = await fetchApi("/serial_cur"); 10 | return res; 11 | } 12 | 13 | export async function postSerialCur(Serial, Baud) { 14 | const res = await fetchApi("/serial_cur", "POST", { Serial, Baud }); 15 | return res; 16 | } 17 | 18 | export async function deleteSerialCur() { 19 | return await fetchApi("/serial_cur", "DELETE"); 20 | } 21 | -------------------------------------------------------------------------------- /src/api/variable.js: -------------------------------------------------------------------------------- 1 | import { fetchApi, uploadApi } from "./internal"; 2 | 3 | export async function getVariable(mode) { 4 | const res = await fetchApi("/variable_" + mode); 5 | return res; 6 | } 7 | 8 | export async function getVariableType() { 9 | const res = await fetchApi("/variable_type"); 10 | return res.Types; 11 | } 12 | 13 | export async function postVariable(mode, Board, Name, Type, Addr, Inputcolor, SignalGain, SignalBias) { 14 | return await fetchApi("/variable_" + mode, "POST", { Board, Name, Type, Addr, Inputcolor, SignalGain, SignalBias }); 15 | } 16 | 17 | export async function postVariableToProj(file) { 18 | return await uploadApi("/variable_proj", "POST", file); 19 | } 20 | 21 | export async function putVariable(mode, Board, Name, Type, Addr, Data, Inputcolor) { 22 | return await fetchApi("/variable_" + mode, "PUT", { Board, Name, Type, Addr, Data, Inputcolor }); 23 | } 24 | 25 | export async function deleteVariable(mode, Board, Name, Type, Addr, Inputcolor) { 26 | return await fetchApi("/variable_" + mode, "DELETE", { Board, Name, Type, Addr, Inputcolor }); 27 | } 28 | export async function deleteVariableAll() { 29 | return await fetchApi("/variable_proj", "DELETE", "vToProj.json"); 30 | } 31 | -------------------------------------------------------------------------------- /src/api/version.js: -------------------------------------------------------------------------------- 1 | import { fetchApi } from "./internal"; 2 | 3 | export async function getVersion() { 4 | const res = await fetchApi("https://api.github.com/repos/scutrobotlab/asuwave/releases/latest", "GET"); 5 | return res; 6 | } 7 | 8 | export async function postUpdate() { 9 | const res = await fetchApi("/update", "POST"); 10 | return res; 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scutrobotlab/asuwave/dbe0b8d43200bf2a322fb887b1f8f7460b4d9f07/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/AboutDialog/AboutDialog.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 65 | -------------------------------------------------------------------------------- /src/components/AboutDialog/AboutOption.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 61 | 62 | -------------------------------------------------------------------------------- /src/components/AboutDialog/AboutVersion.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 82 | 83 | -------------------------------------------------------------------------------- /src/components/ChartCard.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 168 | 169 | 177 | -------------------------------------------------------------------------------- /src/components/DrawerList.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /src/components/ErrorAlert.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /src/components/SerialPort.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 86 | -------------------------------------------------------------------------------- /src/components/VariableAllDialog.vue: -------------------------------------------------------------------------------- 1 | 106 | 107 | 246 | -------------------------------------------------------------------------------- /src/components/VariableNewDialog.vue: -------------------------------------------------------------------------------- 1 | 101 | 102 | 217 | -------------------------------------------------------------------------------- /src/components/VariableReadList.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 82 | -------------------------------------------------------------------------------- /src/components/VariableWriteList.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 81 | -------------------------------------------------------------------------------- /src/const/BaudRate.json: -------------------------------------------------------------------------------- 1 | [ 2 | 19200, 3 | 28800, 4 | 38400, 5 | 57600, 6 | 76800, 7 | 115200, 8 | 230400, 9 | 460800, 10 | 576000, 11 | 921600 12 | ] -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import vuetify from "./plugins/vuetify"; 4 | import store from "./store"; 5 | 6 | import "@mdi/font/css/materialdesignicons.css"; 7 | import "@fontsource/roboto"; 8 | 9 | Vue.config.productionTip = false; 10 | 11 | new Vue({ 12 | vuetify, 13 | store, 14 | beforeCreate() { 15 | Vue.prototype.$bus = this; 16 | }, 17 | render: (h) => h(App), 18 | }).$mount("#app"); 19 | -------------------------------------------------------------------------------- /src/mixins/errorMixin.js: -------------------------------------------------------------------------------- 1 | import ErrorAlert from "@/components/ErrorAlert.vue"; 2 | 3 | export default { 4 | data: () => ({ 5 | error: null, 6 | }), 7 | methods: { 8 | clearError() { 9 | this.error = null; 10 | }, 11 | async errorHandler(func) { 12 | this.clearError(); 13 | try { 14 | const res = await func; 15 | return res; 16 | } catch (error) { 17 | this.error = error; 18 | throw error; 19 | } 20 | }, 21 | }, 22 | components: { 23 | ErrorAlert, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuetify from "vuetify/lib/framework"; 3 | 4 | import colors from "vuetify/lib/util/colors"; 5 | 6 | Vue.use(Vuetify); 7 | 8 | export default new Vuetify({ 9 | theme: { 10 | themes: { 11 | light: { 12 | primary: colors.blue.lighten1, 13 | primaryText: colors.blue.base, 14 | secondary: colors.cyan.lighten1, 15 | accent: colors.cyan.accent2, 16 | }, 17 | dark: { 18 | primary: colors.blue.darken4, 19 | primaryText: colors.blue.lighten1, 20 | secondary: colors.cyan.darken4, 21 | accent: colors.cyan.accent1, 22 | }, 23 | }, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import File from "./modules/File"; 4 | import Option from "./modules/Option"; 5 | import Version from "./modules/Version"; 6 | import Variables from "./modules/Variables"; 7 | import SerialPort from "./modules/SerialPort"; 8 | 9 | Vue.use(Vuex); 10 | 11 | export default new Vuex.Store({ 12 | state: {}, 13 | mutations: {}, 14 | actions: {}, 15 | modules: { 16 | file: File, 17 | option: Option, 18 | version: Version, 19 | variables: Variables, 20 | serialPort: SerialPort, 21 | }, 22 | }); -------------------------------------------------------------------------------- /src/store/modules/File.js: -------------------------------------------------------------------------------- 1 | import { uploadFile, setFilePath, getFilePath} from "@/api/file.js"; //postVariable, 2 | 3 | export default { 4 | namespaced: true, 5 | state: { 6 | path: "", 7 | upload: "", 8 | error: null 9 | }, 10 | getters: { 11 | fileStatus (state) { 12 | if (state.path != "") { 13 | return "正在监听:" + state.path 14 | } else if (state.upload != "") { 15 | return "已导入:" + state.upload 16 | } else { 17 | return "未设置文件" 18 | } 19 | } 20 | }, 21 | 22 | mutations: { 23 | setError (state, err) { 24 | state.error = err 25 | } 26 | }, 27 | actions: { 28 | async refreshPath ({ state }) { 29 | await getFilePath().then((r)=>{ 30 | window.console.log("Watching file: ", r); 31 | if (r != null) { 32 | state.upload = ""; 33 | state.path = r; 34 | } 35 | }); 36 | }, 37 | async setUpload ({ state }, f) { 38 | await uploadFile(f).then(()=>{ 39 | state.upload = f.name; 40 | state.path = ""; 41 | }); 42 | }, 43 | async setPath ({ state }, f) { 44 | await setFilePath(f).then(()=>{ 45 | state.upload = ""; 46 | state.path = f; 47 | }); 48 | }, 49 | }, 50 | modules: {}, 51 | }; -------------------------------------------------------------------------------- /src/store/modules/Option.js: -------------------------------------------------------------------------------- 1 | import { getOption, setOption } from "@/api/option.js"; //postVariable, 2 | 3 | export default { 4 | namespaced: true, 5 | state: { 6 | LogLevel: 0, 7 | SaveVarList: false, 8 | SaveFilePath: false, 9 | UpdateByProj: false 10 | }, 11 | getters: {}, 12 | mutations: { 13 | all(state, s) { 14 | state.LogLevel = s.LogLevel 15 | state.SaveVarList = s.SaveVarList 16 | state.SaveFilePath = s.SaveFilePath 17 | state.UpdateByProj = s.UpdateByProj 18 | }, 19 | kv(state, {k, v}) { 20 | state[k] = v 21 | } 22 | }, 23 | actions: { 24 | async get ({ commit }) { 25 | await getOption().then((r)=>{ 26 | commit("all", r) 27 | }); 28 | }, 29 | async set ({ commit }, {k, v}) { 30 | window.console.log(k, v); 31 | await setOption(k, v).then(()=>{ 32 | commit("kv", {k, v}); 33 | }); 34 | }, 35 | }, 36 | modules: {}, 37 | }; -------------------------------------------------------------------------------- /src/store/modules/SerialPort.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | state: { 4 | status: false, 5 | }, 6 | getters: {}, 7 | 8 | mutations: { 9 | setStatus(state, i) { 10 | state.status = i; 11 | }, 12 | }, 13 | actions: {}, 14 | modules: {}, 15 | }; -------------------------------------------------------------------------------- /src/store/modules/Variables.js: -------------------------------------------------------------------------------- 1 | import { getVariable, getVariableType } from "@/api/variable.js"; 2 | 3 | export default { 4 | namespaced: true, 5 | state: { 6 | proj: [], 7 | read: {}, 8 | write: {}, 9 | vTypes: [], 10 | }, 11 | getters: {}, 12 | mutations: { 13 | setV(state, i) { 14 | if (i.t == "proj") { 15 | state[i.t] = Object.values(i.v); 16 | return; 17 | } 18 | state[i.t] = i.v; 19 | }, 20 | setVType(state, i) { 21 | state.vTypes = i; 22 | }, 23 | }, 24 | actions: { 25 | getV({ commit }, t) { 26 | getVariable(t).then((v) => { 27 | commit("setV", { t, v }); 28 | }); 29 | }, 30 | getVType({ commit }) { 31 | getVariableType().then((v) => { 32 | commit("setVType", v); 33 | }); 34 | }, 35 | }, 36 | modules: {}, 37 | }; -------------------------------------------------------------------------------- /src/store/modules/Version.js: -------------------------------------------------------------------------------- 1 | import { getVersion } from "@/api/version.js"; 2 | 3 | export default { 4 | namespaced: true, 5 | state: { 6 | os: "", 7 | arch: "", 8 | current_tag: "", 9 | update: { 10 | error: false, 11 | checking: true, 12 | response: { 13 | tag_name: "", 14 | body: "", 15 | assets: [], 16 | }, 17 | }, 18 | }, 19 | getters: { 20 | asset(state) { 21 | return state.update.response.assets.find((asset) => { 22 | return ( 23 | asset.browser_download_url.includes(state.os) && 24 | asset.browser_download_url.includes(state.arch) 25 | ); 26 | }); 27 | }, 28 | NewVersion(state) { 29 | function compare(v1, v2) { 30 | v1.slice(1); 31 | v2.slice(1); 32 | v1 = v1.split('.'); 33 | v2 = v2.split('.'); 34 | const k = Math.min(v1.length, v2.length); 35 | for (let i = 0; i < k; ++ i) { 36 | v1[i] = parseInt(v1[i], 10); 37 | v2[i] = parseInt(v2[i], 10); 38 | if (v1[i] > v2[i]) return false; 39 | if (v1[i] < v2[i]) return true; 40 | } 41 | return v1.length == v2.length ? false: (v1.length < v2.length ? true : false); 42 | } 43 | return (state.update.response.tag_name != "" && compare(state.current_tag, state.update.response.tag_name)); 44 | } 45 | }, 46 | mutations: { 47 | Init(state) { 48 | state.current_tag = process.env.VUE_APP_GITTAG; 49 | window.console.log(state.current_tag); 50 | 51 | if (/Win|win/i.test(navigator.userAgent)) state.os = "windows"; 52 | else if (/Mac|mac|darwin/i.test(navigator.userAgent)) state.os = "darwin"; 53 | else if (/linux|gnu/i.test(navigator.userAgent)) state.os = "linux"; 54 | 55 | if (/(?:(amd|x(?:(?:86|64)[-_])?|wow|win)64)[;)]/i.test(navigator.userAgent)) 56 | state.arch = "amd64"; 57 | else if (/\b(aarch64|arm(v?8e?l?|_?64))\b/i.test(navigator.userAgent)) state.arch = "arm64"; 58 | 59 | window.console.log(navigator.userAgent); 60 | }, 61 | }, 62 | actions: { 63 | async Init({dispatch, commit}) { 64 | commit("Init"); 65 | dispatch("CheckUpdate") 66 | }, 67 | async CheckUpdate({state}) { 68 | state.update.checking = true; 69 | state.update.response = await getVersion(); 70 | state.update.checking = false; 71 | }, 72 | }, 73 | modules: {}, 74 | }; -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transpileDependencies: [ 3 | 'vuetify' 4 | ], 5 | 6 | devServer: { 7 | proxy: 'http://localhost:8000' 8 | }, 9 | 10 | productionSourceMap: false 11 | } 12 | --------------------------------------------------------------------------------