├── doc ├── p1.avif ├── p2.avif ├── p3.avif ├── p4.avif ├── p5.avif └── p6.avif ├── .gitmodules ├── html ├── libs │ ├── kissfft.131.2.0.wasm │ ├── uPlot-1.6.32.min.css │ ├── xterm-addon-fit-0.11.0-beta.129.js │ ├── msgpack-ygoe-9045d01.min.js │ └── xterm-5.6.0-beta.129.css ├── common.js ├── utils │ ├── lang.js │ ├── cd_ws.js │ ├── trans │ │ ├── zh_hk.js │ │ └── zh_cn.js │ ├── idb.js │ └── helper.js ├── ctrl.html ├── common.css ├── index.html ├── plugins │ ├── export.js │ ├── pic.js │ ├── plot_fft.js │ ├── dbg.js │ ├── plot_zoom.js │ ├── iap.js │ └── reg_rw.js ├── ctrl.js └── index.js ├── .gitignore ├── License ├── plugins └── iap.py ├── cd_ws.py ├── configs ├── cdpump-v5.json ├── cdbus-bridge-v7.json ├── cdcam-v3.json ├── cdpump-v6.json ├── cdstep-v6.json ├── cdfoc-v6.json └── cdfoc-v6-sensorless.json ├── web_serve.py ├── tools ├── cdg_iap.py ├── cdg_helper.py └── cdg_cmd.py ├── main.py ├── Readme.md └── main_udp.py /doc/p1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dukelec/cdbus_gui/HEAD/doc/p1.avif -------------------------------------------------------------------------------- /doc/p2.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dukelec/cdbus_gui/HEAD/doc/p2.avif -------------------------------------------------------------------------------- /doc/p3.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dukelec/cdbus_gui/HEAD/doc/p3.avif -------------------------------------------------------------------------------- /doc/p4.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dukelec/cdbus_gui/HEAD/doc/p4.avif -------------------------------------------------------------------------------- /doc/p5.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dukelec/cdbus_gui/HEAD/doc/p5.avif -------------------------------------------------------------------------------- /doc/p6.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dukelec/cdbus_gui/HEAD/doc/p6.avif -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pycdnet"] 2 | path = pycdnet 3 | url = ../pycdnet 4 | 5 | -------------------------------------------------------------------------------- /html/libs/kissfft.131.2.0.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dukelec/cdbus_gui/HEAD/html/libs/kissfft.131.2.0.wasm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE! Don't add files that are generated in specific 3 | # subdirectories here. Add them in the ".gitignore" file 4 | # in that subdirectory instead. 5 | # 6 | # NOTE! Please use 'git ls-files -i --exclude-standard' 7 | # command after changing this file, to see if there are 8 | # any tracked files which get ignored after the change. 9 | # 10 | # Normal rules 11 | # 12 | .* 13 | *.pyc 14 | 15 | # 16 | # Top-level generic files 17 | # 18 | /tags 19 | /TAGS 20 | 21 | # 22 | # Debian directory (make deb-pkg) 23 | # 24 | #/temp/ 25 | 26 | # 27 | # git files that we don't want to ignore even it they are dot-files 28 | # 29 | !.gitignore 30 | !.gitkeep 31 | !.mailmap 32 | !.gitmodules 33 | 34 | # stgit generated dirs 35 | patches-* 36 | 37 | # quilt's files 38 | patches 39 | series 40 | 41 | # cscope files 42 | cscope.* 43 | ncscope.* 44 | 45 | # gnu global files 46 | GPATH 47 | GRTAGS 48 | GSYMS 49 | GTAGS 50 | 51 | *.orig 52 | *~ 53 | \#*# 54 | 55 | -------------------------------------------------------------------------------- /html/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | let csa = { 8 | arg: {}, // url args 9 | db: null, 10 | 11 | ws_ns: null, 12 | cmd_sock: null, 13 | 14 | cfg: {}, // device config 15 | plugins: [] // registered plugins 16 | }; 17 | 18 | 19 | async function alloc_port(port=null) { 20 | csa.cmd_sock.flush(); 21 | if (port == 'clr_all') { 22 | await csa.cmd_sock.sendto({'action': 'clr_all'}, ['server', 'port']); 23 | let ret = await csa.cmd_sock.recvfrom(1000); 24 | console.log(`clr_all ports ret: ${ret[0]}`); 25 | } else { 26 | await csa.cmd_sock.sendto({'action': 'get_port', 'port': port}, ['server', 'port']); 27 | let ret = await csa.cmd_sock.recvfrom(1000); 28 | //console.log(`alloc port: ${ret[0]}`); 29 | return ret[0]; 30 | } 31 | } 32 | 33 | export { csa, alloc_port }; 34 | 35 | -------------------------------------------------------------------------------- /html/utils/lang.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | import { trans_zh_hk } from './trans/zh_hk.js' 8 | import { trans_zh_cn } from './trans/zh_cn.js' 9 | //import { trans_ja } from './trans/ja.js' 10 | //import { trans_ko } from './trans/ko.js' 11 | //import { trans_de } from './trans/de.js' 12 | 13 | let trans = null; 14 | if (navigator.language.startsWith('zh')) { 15 | trans = trans_zh_hk; 16 | if (navigator.language.includes('CN')) 17 | trans = trans_zh_cn; 18 | } 19 | //if (navigator.language.includes('ja')) // Japanese 20 | // trans = trans_ja; 21 | //if (navigator.language.includes('ko')) // Korean 22 | // trans = trans_ko; 23 | //if (navigator.language.includes('de')) // German 24 | // trans = trans_de; 25 | 26 | function L(ori, mark=null) { 27 | if (trans == null) 28 | return ori; 29 | if (!mark) 30 | mark = ori; 31 | return trans[mark] || ori; 32 | } 33 | 34 | export { L }; 35 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 DUKELEC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /html/ctrl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CDBUS Tools - Ctrl 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |

${L('Device')}

20 |
21 | 22 | ${L('Device Info')}: -- 23 |
24 |
25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /plugins/iap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Software License Agreement (MIT License) 4 | # 5 | # Author: Duke Fong 6 | 7 | from intelhex import IntelHex 8 | from cd_ws import CDWebSocket 9 | from web_serve import ws_ns 10 | from cdnet.utils.log import * 11 | 12 | 13 | async def iap_service(): # config r/w 14 | logger = logging.getLogger(f'cdgui.iap') 15 | sock = CDWebSocket(ws_ns, 'iap') 16 | while True: 17 | dat, src = await sock.recvfrom() 18 | logger.debug(f'iap ser: {dat}') 19 | 20 | if dat['action'] == 'get_ihex': 21 | ret = [] 22 | ih = IntelHex() 23 | try: 24 | ih.loadhex(dat['path']) 25 | segs = ih.segments() 26 | logger.info(f'parse ihex file, segments: {[list(map(hex, l)) for l in segs]} (end addr inclusive)') 27 | for seg in segs: 28 | s = [seg[0], ih.tobinstr(seg[0], size=seg[1]-seg[0])] 29 | ret.append(s) 30 | except Exception as err: 31 | logger.error(f'parse ihex file error: {err}') 32 | await sock.sendto(ret, src) 33 | 34 | else: 35 | await sock.sendto('err: iap: unknown cmd', src) 36 | 37 | def iap_init(csa): 38 | csa['async_loop'].create_task(iap_service()) 39 | 40 | -------------------------------------------------------------------------------- /html/common.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | .columns.is-gapless>.is-1.reg_btn_rw { 8 | width: 30px; 9 | margin: 1px 1px 1px 1px; 10 | border-radius: 6px 6px 6px 6px; 11 | border-width: 0.1px; 12 | border-style: dashed; 13 | 14 | /* txt center: */ 15 | display: flex; 16 | justify-content: center; /* align horizontal */ 17 | align-items: center; /* align vertical */ 18 | } 19 | .reg_btn_rw:hover { 20 | cursor: pointer; 21 | } 22 | 23 | .columns.is-gapless:not(:last-child) { 24 | margin-bottom: 0rem; 25 | } 26 | 27 | .resizable { 28 | position: relative; 29 | resize: vertical; 30 | overflow: auto; 31 | } 32 | 33 | .resizable::after { 34 | content: ""; 35 | position: absolute; 36 | right: 0px; 37 | bottom: 0px; 38 | width: 14px; 39 | height: 14px; 40 | background: #cdcdcdcd 41 | url("data:image/svg+xml;utf8, \ 42 | \ 43 | \ 44 | \ 45 | ") 46 | no-repeat right 0px bottom 0px; 47 | background-size: 14px 14px; 48 | cursor: se-resize; 49 | } 50 | -------------------------------------------------------------------------------- /html/utils/cd_ws.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | import { Queue } from './helper.js' 8 | 9 | // packet format: 10 | // { 11 | // src: [addr, port], dst: [addr, port], 12 | // dat: ... 13 | // } 14 | 15 | 16 | class CDWebSocket { 17 | constructor(ns, port) { 18 | this.ns = ns; 19 | this.port = port; 20 | this.recv_q = new Queue(); 21 | console.assert(!(this.port in this.ns.sockets)); 22 | this.ns.sockets[this.port] = this; 23 | } 24 | 25 | delete_() { 26 | delete this.ns.sockets[this.port]; 27 | } 28 | 29 | async sendto(dat, s_addr) { 30 | let msg = msgpack.serialize({'src': [this.ns.addr, this.port], 'dst': s_addr, 'dat': dat}); 31 | if (s_addr[0] in this.ns.connections) 32 | await this.ns.connections[s_addr[0]].send(msg); 33 | else if (this.ns.def_route && (this.ns.def_route in this.ns.connections)) 34 | await this.ns.connections[this.ns.def_route].send(msg); 35 | } 36 | 37 | async recvfrom(timeout=null) { 38 | return await this.recv_q.get(timeout=timeout); 39 | } 40 | 41 | flush() { 42 | this.recv_q.flush(); 43 | } 44 | } 45 | 46 | 47 | class CDWebSocketNS { 48 | constructor(addr, def_route=null) { 49 | this.addr = addr; 50 | this.def_route = def_route; 51 | this.connections = {}; 52 | this.sockets = {}; 53 | } 54 | } 55 | 56 | export { CDWebSocket, CDWebSocketNS }; 57 | -------------------------------------------------------------------------------- /html/utils/trans/zh_hk.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, Kudo, Inc. 3 | * All rights reserved. 4 | * 5 | * Author: Duke Fong 6 | */ 7 | 8 | let trans_zh_hk = { 9 | // index 10 | 'Index': '首頁', 11 | 'Serial': '串口', 12 | 'Refresh': '刷新', 13 | 'Open': '打開', 14 | 'Close': '關閉', 15 | 'Current': '當前', 16 | 17 | 'Available': '可用', 18 | 19 | 'Devices': '設備', 20 | 'Set': '設置', 21 | 22 | 'Scroll end': '滾動到最後', 23 | 'Max Len': '最大長度', 24 | 'Clear': '清除', 25 | 'Prev': '上一個', 26 | 'Next': '下一個', 27 | 28 | 'Online': '在綫', 29 | 'Offline': '離線', 30 | 'Open Window': '打開頁面', 31 | 32 | // ctrl 33 | 'Regs': '寄存器', 34 | 'Read Info': '讀設備信息', 35 | 'Read All': '讀取全部', 36 | 'Write All': '寫入全部', 37 | 38 | 'Less': '簡潔模式', 39 | 'Read per': '讀取頻率', 40 | 41 | 'Button Edit': '按鍵編輯', 42 | 'Group': '合併組', 43 | 'Ungroup': '取消組', 44 | 'Enable': '使能', 45 | 'Disable': '禁用', 46 | 'Select All': '選擇全部', 47 | 'Load Default': '加載預設', 48 | 49 | 'Device Info': '設備信息', 50 | 'Depth': '深度', 51 | 'Realtime': '實時', 52 | 'Re-Calc': '更新計算', 53 | 54 | 'Reboot': '重啟', 55 | 'Flash': '燒錄', 56 | 'Enter': '進入', 57 | 'Flash Only': '僅燒錄', 58 | 59 | 'No Check': '不檢查', 60 | 'Read Back Check': '回讀檢查', 61 | 'Read CRC Check': '讀 CRC 檢查', 62 | 63 | 'Start': '開始', 64 | 'Stop': '停止', 65 | 'Progress': '進度', 66 | 67 | 'Export': '導出', 68 | 'Import': '導入', 69 | 'Export Data': '導出數據', 70 | 'Import Data': '導入數據', 71 | 72 | 'Serial disconnected': '串口斷開連接' 73 | 74 | }; 75 | 76 | export { trans_zh_hk }; 77 | -------------------------------------------------------------------------------- /cd_ws.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Software License Agreement (MIT License) 4 | # 5 | # Author: Duke Fong 6 | # 7 | 8 | # Packet format: 9 | # { 10 | # src: (addr, port), dst: (addr, port), 11 | # dat: ... 12 | # } 13 | # 14 | # The addr and port format: any string (or number) 15 | # The dat format: dict for convention 16 | 17 | import umsgpack 18 | import asyncio 19 | 20 | 21 | class CDWebSocket(): 22 | def __init__(self, ns, port): 23 | self.ns = ns 24 | self.port = port 25 | self.recv_q = asyncio.Queue() 26 | assert self.port not in self.ns.sockets 27 | self.ns.sockets[self.port] = self 28 | 29 | def delete(self): 30 | #await self.recv_q.join() 31 | del self.ns.sockets[self.port] 32 | 33 | async def sendto(self, dat, s_addr): 34 | msg = umsgpack.packb({'src': (self.ns.addr, self.port), 'dst': s_addr, 'dat': dat}) 35 | if s_addr[0] in self.ns.connections: 36 | await self.ns.connections[s_addr[0]].send(msg) 37 | return None 38 | elif self.ns.def_route and (self.ns.def_route in self.ns.connections): 39 | await self.ns.connections[self.ns.def_route].send(msg) 40 | return None 41 | else: 42 | return 'no route' 43 | 44 | async def recvfrom(self, timeout=None): 45 | # throw asyncio.TimeoutError if timeout 46 | return await asyncio.wait_for(self.recv_q.get(), timeout=timeout) 47 | 48 | 49 | class CDWebSocketNS(): 50 | def __init__(self, addr, def_route=None): 51 | self.addr = addr 52 | self.def_route = def_route 53 | self.connections = {} # id: ws 54 | self.sockets = {} # port: CDWebSocket 55 | 56 | 57 | # cd_ws_def_ns = CDWebSocketNS('server') 58 | 59 | -------------------------------------------------------------------------------- /html/utils/trans/zh_cn.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, Kudo, Inc. 3 | * All rights reserved. 4 | * 5 | * Author: Duke Fong 6 | */ 7 | 8 | // cat zh_hk.js | cconv -f UTF8 -t UTF8-CN 9 | let trans_zh_cn = { 10 | // index 11 | 'Index': '首页', 12 | 'Serial': '串口', 13 | 'Refresh': '刷新', 14 | 'Open': '打开', 15 | 'Close': '关闭', 16 | 'Current': '当前', 17 | 18 | 'Available': '可用', 19 | 20 | 'Devices': '设备', 21 | 'Set': '设置', 22 | 23 | 'Scroll end': '滚动到最后', 24 | 'Max Len': '最大长度', 25 | 'Clear': '清除', 26 | 'Prev': '上一个', 27 | 'Next': '下一个', 28 | 29 | 'Online': '在线', 30 | 'Offline': '离线', 31 | 'Open Window': '打开页面', 32 | 33 | // ctrl 34 | 'Regs': '寄存器', 35 | 'Read Info': '读设备信息', 36 | 'Read All': '读取全部', 37 | 'Write All': '写入全部', 38 | 39 | 'Less': '简洁模式', 40 | 'Read per': '读取频率', 41 | 42 | 'Button Edit': '按键编辑', 43 | 'Group': '合并组', 44 | 'Ungroup': '取消组', 45 | 'Enable': '使能', 46 | 'Disable': '禁用', 47 | 'Select All': '选择全部', 48 | 'Load Default': '加载默认', 49 | 50 | 'Device Info': '设备信息', 51 | 'Depth': '深度', 52 | 'Realtime': '实时', 53 | 'Re-Calc': '更新计算', 54 | 55 | 'Reboot': '重启', 56 | 'Flash': '烧录', 57 | 'Enter': '进入', 58 | 'Flash Only': '仅烧录', 59 | 60 | 'No Check': '不检查', 61 | 'Read Back Check': '回读检查', 62 | 'Read CRC Check': '读 CRC 检查', 63 | 64 | 'Start': '开始', 65 | 'Stop': '停止', 66 | 'Progress': '进度', 67 | 68 | 'Export': '导出', 69 | 'Import': '导入', 70 | 'Export Data': '导出数据', 71 | 'Import Data': '导入数据', 72 | 73 | 'Serial disconnected': '串口断开连接' 74 | 75 | }; 76 | 77 | export { trans_zh_cn }; 78 | -------------------------------------------------------------------------------- /html/libs/uPlot-1.6.32.min.css: -------------------------------------------------------------------------------- 1 | .uplot, .uplot *, .uplot *::before, .uplot *::after {box-sizing: border-box;}.uplot {font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height: 1.5;width: min-content;}.u-title {text-align: center;font-size: 18px;font-weight: bold;}.u-wrap {position: relative;user-select: none;}.u-over, .u-under {position: absolute;}.u-under {overflow: hidden;}.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}.u-axis {position: absolute;}.u-legend {font-size: 14px;margin: auto;text-align: center;}.u-inline {display: block;}.u-inline * {display: inline-block;}.u-inline tr {margin-right: 16px;}.u-legend th {font-weight: 600;}.u-legend th > * {vertical-align: middle;display: inline-block;}.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;background-clip: padding-box !important;}.u-inline.u-live th::after {content: ":";vertical-align: middle;}.u-inline:not(.u-live) .u-value {display: none;}.u-series > * {padding: 4px;}.u-series th {cursor: pointer;}.u-legend .u-off > * {opacity: 0.3;}.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;}.u-hz .u-cursor-x, .u-vt .u-cursor-y {height: 100%;border-right: 1px dashed #607D8B;}.u-hz .u-cursor-y, .u-vt .u-cursor-x {width: 100%;border-bottom: 1px dashed #607D8B;}.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;border: 0 solid;pointer-events: none;will-change: transform;/*this has to be !important since we set inline "background" shorthand */background-clip: padding-box !important;}.u-axis.u-off, .u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;} -------------------------------------------------------------------------------- /html/libs/xterm-addon-fit-0.11.0-beta.129.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2024 The xterm.js authors. All rights reserved. 3 | * @license MIT 4 | * 5 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 6 | * @license MIT 7 | * 8 | * Originally forked from (with the author's permission): 9 | * Fabrice Bellard's javascript vt100 for jslinux: 10 | * http://bellard.org/jslinux/ 11 | * Copyright (c) 2011 Fabrice Bellard 12 | */ 13 | /*--------------------------------------------------------------------------------------------- 14 | * Copyright (c) Microsoft Corporation. All rights reserved. 15 | * Licensed under the MIT License. See License.txt in the project root for license information. 16 | *--------------------------------------------------------------------------------------------*/ 17 | var h=2,_=1,o=class{activate(e){this._terminal=e}dispose(){}fit(){let e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;let t=this._terminal._core;(this._terminal.rows!==e.rows||this._terminal.cols!==e.cols)&&(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;let t=this._terminal._core._renderService.dimensions;if(t.css.cell.width===0||t.css.cell.height===0)return;let s=this._terminal.options.scrollback===0?0:this._terminal.options.overviewRuler?.width||14,r=window.getComputedStyle(this._terminal.element.parentElement),l=parseInt(r.getPropertyValue("height")),a=Math.max(0,parseInt(r.getPropertyValue("width"))),i=window.getComputedStyle(this._terminal.element),n={top:parseInt(i.getPropertyValue("padding-top")),bottom:parseInt(i.getPropertyValue("padding-bottom")),right:parseInt(i.getPropertyValue("padding-right")),left:parseInt(i.getPropertyValue("padding-left"))},m=n.top+n.bottom,d=n.right+n.left,c=l-m,p=a-d-s;return{cols:Math.max(h,Math.floor(p/t.css.cell.width)),rows:Math.max(_,Math.floor(c/t.css.cell.height))}}};export{o as FitAddon}; 18 | //# sourceMappingURL=addon-fit.mjs.map 19 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CDBUS Tools - Index 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |

${L('Serial')}

18 |
19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 | ${L('Current')}: 27 |
28 |
29 | 30 |
31 |
32 |

${L('Available')}

33 |
34 |
    35 | 36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |

${L('Devices')}

44 |
45 |
46 |
47 |
48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /configs/cdpump-v5.json: -------------------------------------------------------------------------------- 1 | { 2 | "reg": { 3 | // fmt: [c]: string, b: int8_t, B: uint8_t, h: int16_t, H: uint16_t, i: int32_t, I: uint32_t, f: float 4 | // show: 0: normal, 1: hex, 2: bytes 5 | "list": [ 6 | [ 0x0000, 2, "H", 1, "magic_code", "Magic code: 0xcdcd" ], 7 | [ 0x0002, 2, "H", 1, "conf_ver", "Config version" ], 8 | [ 0x0004, 1, "B", 1, "conf_from", "0: default config, 1: load from flash" ], 9 | [ 0x0005, 1, "b", 0, "do_reboot", "1: reboot to bl, 2: reboot to app" ], 10 | [ 0x0007, 1, "b", 0, "save_conf", "Write 1 to save current config to flash" ], 11 | 12 | [ 0x000c, 1, "B", 1, "bus_cfg_mac", "RS-485 port id, range: 0~254" ], 13 | [ 0x0010, 4, "I", 0, "bus_cfg_baud_l", "RS-485 baud rate for first byte" ], 14 | [ 0x0014, 4, "I", 0, "bus_cfg_baud_h", "RS-485 baud rate for follow bytes" ], 15 | [ 0x0018, 2, "[B]", 1, "bus_cfg_filter", "Multicast address" ], 16 | [ 0x001a, 1, "B", 0, "bus_cfg_mode", "0: Arbitration, 1: Break Sync" ], 17 | [ 0x001c, 2, "H", 0, "bus_cfg_tx_permit_len", "Allow send wait time" ], 18 | [ 0x001e, 2, "H", 0, "bus_cfg_max_idle_len", "Max idle wait time for BS mode" ], 19 | [ 0x0020, 1, "B", 0, "bus_cfg_tx_pre_len", " Active TX_EN before TX" ], 20 | 21 | [ 0x0024, 1, "b", 0, "dbg_en", "1: Report debug message to host, 0: do not report" ], 22 | 23 | // --------------- Follows are not writable: ------------------- 24 | [ 0x0036, 1, "B", 0, "gpo_pins", "GPIO output pins" ], 25 | ], 26 | 27 | // button groups 28 | "reg_r": [["magic_code","save_conf"],["bus_cfg_mac","bus_cfg_tx_pre_len"],["dbg_en"],["gpo_pins"]], 29 | "reg_w": [["magic_code"],["do_reboot"],["save_conf"],["bus_cfg_mac"],["bus_cfg_baud_l","bus_cfg_baud_h"], 30 | ["bus_cfg_filter","bus_cfg_max_idle_len"],["bus_cfg_tx_pre_len"],["dbg_en"],["gpo_pins"]], 31 | "less_r": [], 32 | "less_w": [] 33 | }, 34 | 35 | "iap": { "reboot": 0x0005, "keep_bl": 0x0006 } 36 | } 37 | -------------------------------------------------------------------------------- /configs/cdbus-bridge-v7.json: -------------------------------------------------------------------------------- 1 | { 2 | "reg": { 3 | // fmt: [c]: string, b: int8_t, B: uint8_t, h: int16_t, H: uint16_t, i: int32_t, I: uint32_t, f: float 4 | // show: 0: normal, 1: hex, 2: bytes 5 | "list": [ 6 | [ 0x0000, 2, "H", 1, "magic_code", "Magic code: 0xcdcd" ], 7 | [ 0x0002, 2, "H", 1, "conf_ver", "Config version" ], 8 | [ 0x0004, 1, "B", 1, "conf_from", "0: default config, 1: all from flash, 2: partly from flash" ], 9 | [ 0x0005, 1, "B", 0, "do_reboot", "1: reboot to bl, 2: reboot to app" ], 10 | [ 0x0007, 1, "b", 0, "save_conf", "Write 1 to save current config to flash" ], 11 | 12 | [ 0x0008, 1, "b", 0, "dbg_en", "1: Report debug message to host, 0: do not report" ], 13 | 14 | [ 0x0010, 1, "B", 1, "bus_cfg_mac", "RS-485 port id, range: 0~254" ], 15 | [ 0x0014, 4, "I", 0, "bus_cfg_baud_l", "RS-485 baud rate for first byte" ], 16 | [ 0x0018, 4, "I", 0, "bus_cfg_baud_h", "RS-485 baud rate for follow bytes" ], 17 | [ 0x001c, 2, "[B]", 1, "bus_cfg_filter_m", "Multicast address" ], 18 | [ 0x001e, 1, "B", 0, "bus_cfg_mode", "0: Arbitration, 1: Break Sync" ], 19 | [ 0x0020, 2, "H", 0, "bus_cfg_tx_permit_len", "Allow send wait time" ], 20 | [ 0x0022, 2, "H", 0, "bus_cfg_max_idle_len", "Max idle wait time for BS mode" ], 21 | [ 0x0024, 1, "B", 0, "bus_cfg_tx_pre_len", " Active TX_EN before TX" ], 22 | 23 | [ 0x0028, 4, "I", 0, "limit_baudrate0", "Low baudrate limit for sw1 off" ], 24 | [ 0x002c, 4, "I", 0, "limit_baudrate1", "Low baudrate limit for sw1 on" ] 25 | ], 26 | 27 | "reg_r": [["magic_code","conf_ver"],["conf_from","save_conf"],["dbg_en"],["bus_cfg_mac","bus_cfg_baud_h"], 28 | ["bus_cfg_filter_m","bus_cfg_mode"],["bus_cfg_tx_permit_len","bus_cfg_tx_pre_len"],["limit_baudrate0","limit_baudrate1"]], 29 | "reg_w": [["magic_code"],["conf_ver"],["do_reboot"],["save_conf"],["dbg_en"],["bus_cfg_mac"],["bus_cfg_filter_m"], 30 | ["bus_cfg_mode"],["bus_cfg_tx_permit_len","bus_cfg_tx_pre_len"],["limit_baudrate0","limit_baudrate1"]] 31 | }, 32 | 33 | 34 | "iap": { "reboot": 0x0005 } 35 | } 36 | -------------------------------------------------------------------------------- /html/utils/idb.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | class Idb { 8 | constructor(db_name = 'cd', store_list = ['var', 'tmp']) { 9 | 10 | let _dbp = new Promise((resolve, reject) => { 11 | const openreq = indexedDB.open(db_name, 1); 12 | openreq.onerror = () => reject(openreq.error); 13 | openreq.onsuccess = () => resolve(openreq.result); 14 | // First time setup: create an empty object store 15 | openreq.onupgradeneeded = () => { 16 | console.log(`db: ${db_name}: create stores: ${store_list}`); 17 | for (let i in store_list) 18 | openreq.result.createObjectStore(store_list[i]); 19 | }; 20 | }); 21 | 22 | return (async () => { 23 | this.db = await _dbp; 24 | return this; 25 | })(); 26 | } 27 | 28 | trans(store_name, type) { 29 | let store; 30 | let _sp = new Promise((resolve, reject) => { 31 | const transaction = this.db.transaction(store_name, type); 32 | transaction.oncomplete = () => resolve(); 33 | transaction.onabort = transaction.onerror = () => reject(transaction.error); 34 | store = transaction.objectStore(store_name); 35 | }); 36 | return {_sp, store}; 37 | } 38 | 39 | async get(store_name, key) { 40 | let {_sp, store} = this.trans(store_name, 'readonly'); 41 | let req = store.get(key); 42 | await _sp; 43 | return req.result; 44 | } 45 | async set(store_name, key, value) { 46 | let {_sp, store} = this.trans(store_name, 'readwrite'); 47 | store.put(value, key); 48 | await _sp; 49 | } 50 | async del(store_name, key) { 51 | let {_sp, store} = this.trans(store_name, 'readwrite'); 52 | store.delete(key); 53 | await _sp; 54 | } 55 | async clear(store_name) { 56 | let {_sp, store} = this.trans(store_name, 'readwrite'); 57 | store.clear(); 58 | await _sp; 59 | } 60 | async keys(store_name) { 61 | let {_sp, store} = this.trans(store_name, 'readonly'); 62 | let req = store.getAllKeys(); 63 | await _sp; 64 | return req.result; 65 | } 66 | 67 | stores() { 68 | return this.db.objectStoreNames; 69 | } 70 | } 71 | 72 | export { Idb }; 73 | -------------------------------------------------------------------------------- /web_serve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Software License Agreement (MIT License) 4 | # 5 | # Author: Duke Fong 6 | # 7 | 8 | import os 9 | import asyncio 10 | import mimetypes 11 | import umsgpack 12 | import logging 13 | import websockets 14 | from websockets.server import serve 15 | from http import HTTPStatus 16 | from cd_ws import CDWebSocket, CDWebSocketNS 17 | 18 | ws_ns = CDWebSocketNS('server') 19 | logger = logging.getLogger(f'cdgui.web') 20 | 21 | 22 | async def http_file_server(path, request): 23 | if "upgrade" in request.get("Connection", "").lower(): 24 | return None 25 | path = path.split('?')[0] 26 | if path == '/': 27 | path = '/index.html' 28 | response_headers = [ 29 | ('Server', 'asyncio'), 30 | ('Connection', 'close'), 31 | ] 32 | server_root = os.path.join(os.getcwd(), 'html') 33 | full_path = os.path.realpath(os.path.join(server_root, path[1:])) 34 | log_str = f'GET {path}' 35 | 36 | # Validate the path 37 | if os.path.commonpath((server_root, full_path)) != server_root or \ 38 | not os.path.exists(full_path) or not os.path.isfile(full_path): 39 | logger.warning(f'{log_str} 404 NOT FOUND') 40 | return HTTPStatus.NOT_FOUND, response_headers, b'404 NOT FOUND' 41 | 42 | logger.info(f'{log_str} 200 OK') 43 | with open(full_path, 'rb') as f: 44 | body = f.read() 45 | response_headers.append(('Content-Length', str(len(body)))) 46 | response_headers.append(('Content-Type', mimetypes.MimeTypes().guess_type(full_path)[0] or \ 47 | 'application/octet-stream')) 48 | return HTTPStatus.OK, response_headers, body 49 | 50 | 51 | async def ws_handler(ws, path): 52 | try: 53 | logger.info(f'ws: connect, path: {path}') 54 | if path in ws_ns.connections: 55 | logger.warning(f'ws: only allow one connection for: {path}') 56 | return 57 | ws_ns.connections[path] = ws 58 | while True: 59 | msg_ = await ws.recv() 60 | msg = umsgpack.unpackb(msg_) 61 | if msg['dst'][0] != 'server': 62 | logger.warning('ws: addr error') 63 | return 64 | sock = ws_ns.sockets[msg['dst'][1]] 65 | sock.recv_q.put_nowait((msg['dat'], msg['src'])) 66 | 67 | except websockets.exceptions.ConnectionClosed: 68 | pass 69 | #except: 70 | # pass 71 | 72 | del ws_ns.connections[path] 73 | logger.info(f'ws: disconnect, path: {path}') 74 | 75 | 76 | async def start_web(addr='localhost', port=8910): 77 | server = await serve(ws_handler, addr, port, process_request=http_file_server) 78 | await server.wait_closed() 79 | 80 | -------------------------------------------------------------------------------- /configs/cdcam-v3.json: -------------------------------------------------------------------------------- 1 | { 2 | "reg": { 3 | // fmt: [c]: string, b: int8_t, B: uint8_t, h: int16_t, H: uint16_t, i: int32_t, I: uint32_t, f: float 4 | // show: 0: normal, 1: hex, 2: bytes 5 | "list": [ 6 | [ 0x0000, 2, "H", 1, "magic_code", "Magic code: 0xcdcd" ], 7 | [ 0x0002, 2, "H", 1, "conf_ver", "Config version" ], 8 | [ 0x0004, 1, "B", 0, "conf_from", "0: default config, 1: load from flash" ], 9 | [ 0x0005, 1, "b", 0, "do_reboot", "1: reboot to bl, 2: reboot to app" ], 10 | [ 0x0007, 1, "b", 0, "save_conf", "Write 1 to save current config to flash" ], 11 | 12 | [ 0x000c, 1, "B", 1, "bus_cfg_mac", "RS-485 port id, range: 0~254" ], 13 | [ 0x0010, 4, "I", 0, "bus_cfg_baud_l", "RS-485 baud rate for first byte" ], 14 | [ 0x0014, 4, "I", 0, "bus_cfg_baud_h", "RS-485 baud rate for follow bytes" ], 15 | [ 0x0018, 2, "[B]", 1, "bus_cfg_filter", "Multicast address" ], 16 | [ 0x001a, 1, "B", 0, "bus_cfg_mode", "0: Arbitration, 1: Break Sync" ], 17 | [ 0x001c, 2, "H", 0, "bus_cfg_tx_permit_len", "Allow send wait time" ], 18 | [ 0x001e, 2, "H", 0, "bus_cfg_max_idle_len", "Max idle wait time for BS mode" ], 19 | [ 0x0020, 1, "B", 0, "bus_cfg_tx_pre_len", " Active TX_EN before TX" ], 20 | 21 | [ 0x0024, 1, "b", 0, "dbg_en", "1: Report debug message to host, 0: do not report" ], 22 | 23 | [ 0x002c, 3, "[B]", 2, "cam_dst_addr", "Send jpg to this address" ], 24 | [ 0x0030, 2, "H", 1, "cam_dst_port", "Send jpg to this port" ], 25 | 26 | [ 0x0032, 2, "H", 0, "width", "Picture width" ], 27 | [ 0x0034, 2, "H", 0, "height", "Picture height" ], 28 | 29 | [ 0x0042, 2, "H", 0, "manual", "0: auto mode; 1: manual mode" ], 30 | [ 0x0044, 2, "H", 0, "exposure", "Exposure (AEC)" ], 31 | [ 0x0046, 1, "B", 0, "agc", "AGC" ], 32 | 33 | [ 0x005f, 1, "B", 0, "capture", "Write 1 capture single image, write 255 keep capture" ], 34 | [ 0x0069, 1, "b", 0, "led_en", "LED enable / disable" ], 35 | 36 | ], 37 | 38 | "reg_r": [["magic_code","save_conf"],["bus_cfg_mac","bus_cfg_tx_pre_len"],["dbg_en"],["cam_dst_addr","agc"],["capture"],["led_en"]], 39 | "reg_w": [["magic_code"],["do_reboot"],["save_conf"],["bus_cfg_mac"],["bus_cfg_baud_l","bus_cfg_baud_h"], 40 | ["bus_cfg_filter","bus_cfg_max_idle_len"],["bus_cfg_tx_pre_len"],["dbg_en"],["cam_dst_addr","cam_dst_port"], 41 | ["width","agc"],["capture"],["led_en"]], 42 | "less_r": [["capture"]], 43 | "less_w": [["capture"]] 44 | }, 45 | 46 | "pic": { "fmt": "jpeg", "port": 0x10 }, 47 | "iap": { "reboot": 0x0005, "keep_bl": 0x0006 } 48 | } 49 | -------------------------------------------------------------------------------- /html/plugins/export.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | import { L } from '../utils/lang.js' 8 | import { escape_html, date2num, val2hex, dat2str, dat2hex, hex2dat, 9 | read_file, download, readable_size, blob2dat } from '../utils/helper.js'; 10 | import { csa, alloc_port } from '../common.js'; 11 | import { fmt_size, reg2str, read_reg_val, str2reg, write_reg_val, 12 | R_ADDR, R_LEN, R_FMT, R_SHOW, R_ID, R_DESC } from './reg_rw.js'; 13 | 14 | let html = ` 15 |
16 |

${L('Export')} & ${L('Import')}

17 | 18 | 19 | 20 |
21 |
`; 22 | 23 | 24 | function export_data() { 25 | let exp_dat = { 26 | version: 'cdgui v1' 27 | }; 28 | 29 | for (let p of csa.plugins) { 30 | console.log(`export: p: ${p}`); 31 | if ('dat_export' in csa[p]) { 32 | exp_dat[p] = csa[p].dat_export(); 33 | } 34 | } 35 | 36 | console.info('export_data:', exp_dat); 37 | const file_dat = msgpack.serialize(exp_dat); 38 | download(file_dat, csa.arg.name ? `${csa.arg.name}.mpk` : `${csa.arg.tgt}.mpk`); 39 | } 40 | 41 | function import_data() { 42 | //let input = document.createElement('input'); 43 | //cpy(input, {type: 'file', accept: '*.mpk'}, ['type', 'accept']); 44 | let input = document.getElementById('input_file'); 45 | input.accept = '.mpk'; 46 | input.onchange = async function () { 47 | var files = this.files; 48 | if (files && files.length) { 49 | 50 | let file = files[0]; 51 | let data = await read_file(file); 52 | let prj = msgpack.deserialize(data); 53 | if (!prj || !prj.version || !prj.version.startsWith('cdgui')) { 54 | alert(L('Format error')); 55 | this.value = ''; 56 | return; 57 | } 58 | console.log('import dat:', prj); 59 | 60 | if (prj.version == 'cdgui v0') { 61 | prj.reg = prj.reg_str; 62 | prj.dbg = prj.logs; 63 | prj.plot = prj.plots; 64 | alert('Version v0 is deprecated and will be removed next time, please re-export to version v1!'); 65 | } 66 | 67 | for (let p of csa.plugins) { 68 | console.log(`import: p: ${p}`); 69 | if ('dat_import' in csa[p]) { 70 | csa[p].dat_import(prj[p]); 71 | } 72 | } 73 | 74 | alert('Import succeeded'); 75 | } 76 | this.value = ''; 77 | }; 78 | input.click(); 79 | } 80 | 81 | 82 | async function init_export() { 83 | csa.export = {}; 84 | csa.plugins.push('export'); 85 | 86 | document.getElementsByTagName('section')[0].insertAdjacentHTML('beforeend', html); 87 | document.getElementById(`export_btn`).onclick = export_data; 88 | document.getElementById(`import_btn`).onclick = import_data; 89 | } 90 | 91 | export { init_export }; 92 | 93 | -------------------------------------------------------------------------------- /html/plugins/pic.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | import { L } from '../utils/lang.js' 8 | import { escape_html, date2num, timestamp, val2hex, dat2str, dat2hex, hex2dat, 9 | read_file, download, readable_size, blob2dat } from '../utils/helper.js'; 10 | //import { konva_zoom, konva_responsive } from '../utils/konva_helper.js'; 11 | import { CDWebSocket, CDWebSocketNS } from '../utils/cd_ws.js'; 12 | import { Idb } from '../utils/idb.js'; 13 | import { csa, alloc_port } from '../common.js'; 14 | 15 | 16 | async function pic_service() { 17 | let pic_cnt = 1; 18 | let img_dats = []; 19 | let img_dat_len = 0; 20 | let dat_cnt = 0; 21 | 22 | while (true) { 23 | let msg = await csa.pic.sock.recvfrom(); 24 | let dat = msg[0].dat; 25 | let src_port = msg[0].src[1]; 26 | let hdr = src_port; // [5:4] FRAGMENT: 00: error, 01: first, 10: more, 11: last, [3:0]: cnt 27 | 28 | if (hdr == 0x50) { // first 29 | img_dats = []; 30 | img_dats.push(dat); 31 | img_dat_len = dat.length; 32 | dat_cnt = 0; 33 | console.log(`img: header ${dat[0]} ${dat[1]}`); 34 | 35 | } else if ((hdr & 0xf0) == 0x60) { // more 36 | if (dat_cnt == (hdr & 0xf)) { 37 | img_dats.push(dat); 38 | img_dat_len += dat.length; 39 | } else { 40 | console.warn(`pic, wrong cnt, local: ${dat_cnt} != rx: ${hdr & 0xf}, dat len: ${dat.length}`); 41 | } 42 | 43 | } else if ((hdr & 0xf0) == 0x70) { // end 44 | if (dat_cnt == (hdr & 0xf)) { 45 | img_dats.push(dat); 46 | img_dat_len += dat.length; 47 | let img_dat = new Uint8Array(img_dat_len); 48 | let ofs = 0; 49 | for (let d of img_dats) { 50 | img_dat.set(d, ofs); 51 | ofs += d.length; 52 | } 53 | // show pic 54 | if (document.getElementById('pic_id').src) 55 | URL.revokeObjectURL(document.getElementById('pic_id').src); 56 | document.getElementById('pic_id').src = URL.createObjectURL( new Blob([img_dat.buffer], { type: `image/${csa.cfg.pic.fmt}` }) ); 57 | document.getElementById('pic_cnt').innerText = `- #${pic_cnt}`; 58 | pic_cnt++; 59 | // download(img_dat); // debug 60 | 61 | } else { 62 | console.warn(`pic, wrong cnt at end, local: ${dat_cnt} != rx: ${hdr & 0xf}, dat len: ${dat.length}`); 63 | } 64 | } else { // err 65 | console.warn(`pic, receive err, local: ${dat_cnt}, rx: ${hdr & 0xf}, all len: ${img_dat_len}`); 66 | } 67 | 68 | if (++dat_cnt == 0x10) 69 | dat_cnt = 0; 70 | } 71 | } 72 | 73 | 74 | async function init_pic() { 75 | if (!csa.cfg.pic || !csa.cfg.pic.port) { 76 | console.info(`skip init_pic`); 77 | return; 78 | } 79 | csa.pic = {}; 80 | csa.plugins.push('pic'); 81 | 82 | let port = await alloc_port(csa.cfg.pic.port); 83 | console.log(`init_pic, alloc port: ${port}`); 84 | csa.pic.sock = new CDWebSocket(csa.ws_ns, port); 85 | 86 | let html = ` 87 |
88 |

Picture - #--

89 | 90 |

91 |
`; 92 | document.getElementsByTagName('section')[0].insertAdjacentHTML('beforeend', html); 93 | 94 | pic_service(); 95 | } 96 | 97 | export { init_pic }; 98 | 99 | -------------------------------------------------------------------------------- /html/plugins/plot_fft.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | import { csa } from '../common.js'; 8 | import * as FFTModule from '../libs/kissfft.131.2.0.js'; 9 | 10 | let overlay = { 11 | print: text => { 12 | console.log('fft:', text); 13 | }, 14 | printErr: text => { 15 | console.error("fft error:", text); 16 | }, 17 | quit: status => { 18 | console.log("fft quit:", status); 19 | }, 20 | setStatus: text => { 21 | console.log(`fft status: ${text}`); 22 | } 23 | }; 24 | 25 | 26 | function preprocess_signal(signal) { 27 | const len = signal.length; 28 | let mean = 0; 29 | for (let i = 0; i < len; i++) 30 | mean += signal[i]; 31 | mean /= len; 32 | // remove dc bias; add hann window 33 | const out = new Float32Array(len); 34 | for (let i = 0; i < len; i++) { 35 | const x = signal[i] - mean; 36 | const w = 1 - Math.cos((2 * Math.PI * i) / (len - 1)); 37 | out[i] = x * w; 38 | } 39 | return out; 40 | } 41 | 42 | async function plot_fft(idx, dat) { 43 | const fft_mod = csa.plot.fft_mod; 44 | const fft_obj = csa.plot.plot_fft[idx]; 45 | if (dat.length > fft_obj.size) 46 | dat = dat.slice(dat.length - fft_obj.size); 47 | dat = preprocess_signal(dat); 48 | 49 | const input_ptr = fft_mod._calloc(fft_obj.size, 4); 50 | fft_mod.HEAPF32.set(dat, input_ptr / 4); 51 | 52 | const out_len = fft_obj.size / 2 + 1; 53 | const output_ptr = fft_mod._malloc(out_len * 2 * 4); 54 | const output_heap = fft_mod.HEAPF32.subarray(output_ptr / 4, output_ptr / 4 + out_len * 2); 55 | 56 | fft_mod._kiss_fftr(fft_obj.kiss_cfg, input_ptr, output_ptr); 57 | 58 | const mags = new Array(out_len); 59 | const inv_n2 = 1 / (fft_obj.size * fft_obj.size); 60 | for (let k = 0; k < out_len; k++) { 61 | const re = output_heap[2*k]; 62 | const im = output_heap[2*k+1]; 63 | let pwr = (re*re + im*im) * inv_n2; 64 | mags[k] = 10 * Math.log10(Math.max(pwr, 1e-24)); 65 | } 66 | 67 | fft_mod._free(input_ptr); 68 | fft_mod._free(output_ptr); 69 | return mags; 70 | } 71 | 72 | 73 | async function plot_fft_cal(idx, plot_dat) { 74 | const uplot = csa.plot.plots[idx]; 75 | let fft_obj = csa.plot.plot_fft[idx]; 76 | const out_len = fft_obj.size / 2 + 1; 77 | const rate = fft_obj.sample_rate; 78 | const freqs = new Array(out_len); 79 | for (let k = 0; k < out_len; k++) 80 | freqs[k] = k * rate / fft_obj.size; 81 | 82 | let fft_dat = [freqs]; 83 | for (let i = 1; i < plot_dat.length; i++) { 84 | if (uplot.series[i].show) { 85 | let mags = await plot_fft(idx, plot_dat[i]); 86 | fft_dat.push(mags); 87 | } else { 88 | fft_dat.push([]); 89 | } 90 | } 91 | return fft_dat; 92 | } 93 | 94 | 95 | async function plot_fft_init(idx) { 96 | if (!csa.plot.fft_mod) 97 | csa.plot.fft_mod = await FFTModule.default(overlay); 98 | 99 | let fft_obj = csa.plot.plot_fft[idx]; 100 | if (csa.cfg.plot.plots[idx].fft) { 101 | fft_obj.size = csa.cfg.plot.plots[idx].fft.size; 102 | fft_obj.sample_rate = csa.cfg.plot.plots[idx].fft.sample_rate; 103 | } 104 | if (!fft_obj.size) 105 | fft_obj.size = 4096; 106 | if (!fft_obj.sample_rate) 107 | fft_obj.sample_rate = 1; 108 | fft_obj.kiss_cfg = csa.plot.fft_mod._kiss_fftr_alloc(fft_obj.size, 0, null, null); 109 | } 110 | 111 | function plot_fft_deinit(idx) { 112 | let fft_obj = csa.plot.plot_fft[idx]; 113 | if (fft_obj.kiss_cfg) 114 | csa.plot.fft_mod._free(fft_obj.kiss_cfg); 115 | fft_obj.kiss_cfg = null; 116 | } 117 | 118 | 119 | export { 120 | plot_fft_init, plot_fft_deinit, plot_fft_cal 121 | }; 122 | -------------------------------------------------------------------------------- /configs/cdpump-v6.json: -------------------------------------------------------------------------------- 1 | { 2 | "reg": { 3 | // fmt: [c]: string, b: int8_t, B: uint8_t, h: int16_t, H: uint16_t, i: int32_t, I: uint32_t, f: float 4 | // show: 0: normal, 1: hex, 2: bytes 5 | "list": [ 6 | [ 0x0000, 2, "H", 1, "magic_code", "Magic code: 0xcdcd" ], 7 | [ 0x0002, 2, "H", 1, "conf_ver", "Config version" ], 8 | [ 0x0004, 1, "B", 1, "conf_from", "0: default config, 1: all from flash, 2: partly from flash" ], 9 | [ 0x0005, 1, "b", 0, "do_reboot", "1: reboot to bl, 2: reboot to app" ], 10 | [ 0x0007, 1, "b", 0, "save_conf", "Write 1 to save current config to flash" ], 11 | 12 | [ 0x000c, 1, "B", 1, "bus_cfg_mac", "RS-485 port id, range: 0~254" ], 13 | [ 0x0010, 4, "I", 0, "bus_cfg_baud_l", "RS-485 baud rate for first byte" ], 14 | [ 0x0014, 4, "I", 0, "bus_cfg_baud_h", "RS-485 baud rate for follow bytes" ], 15 | [ 0x0018, 2, "[B]", 1, "bus_cfg_filter", "Multicast address" ], 16 | [ 0x001a, 1, "B", 0, "bus_cfg_mode", "0: Arbitration, 1: Break Sync" ], 17 | [ 0x001c, 2, "H", 0, "bus_cfg_tx_permit_len", "Allow send wait time" ], 18 | [ 0x001e, 2, "H", 0, "bus_cfg_max_idle_len", "Max idle wait time for BS mode" ], 19 | [ 0x0020, 1, "B", 0, "bus_cfg_tx_pre_len", " Active TX_EN before TX" ], 20 | 21 | [ 0x0024, 1, "b", 0, "dbg_en", "1: Report debug message to host, 0: do not report" ], 22 | 23 | [ 0x0046, 1, "B", 1, "dbg_raw_msk", "Config which raw debug data to be send" ], 24 | [ 0x0048, 24, "{H,H}", 1, "dbg_raw[0]", "Config raw debug for plot0" ], 25 | 26 | [ 0x0074, 4, "f", 0, "pid_pressure_kp", "" ], 27 | [ 0x0078, 4, "f", 0, "pid_pressure_ki", "" ], 28 | [ 0x007c, 4, "f", 0, "pid_pressure_kd", "" ], 29 | [ 0x0080, 4, "f", 0, "pid_pressure_out_min", "" ], 30 | [ 0x0084, 4, "f", 0, "pid_pressure_out_max", "" ], 31 | 32 | [ 0x00f8, 4, "f", 0, "set_pressure", "" ], 33 | //[ 0x0110, 4, "f", 0, "ori_pressure", "" ], 34 | //[ 0x0114, 4, "f", 0, "bias_pressure", "" ], 35 | 36 | [ 0x0124, 4, "f", 0, "sen_pressure", "kpa" ], 37 | [ 0x0128, 4, "f", 0, "sen_temperature", "c" ], 38 | [ 0x014c, 1, "B", 0, "cur_valve", "" ], 39 | [ 0x014e, 2, "H", 0, "cur_pwm", "" ], 40 | [ 0x0150, 4, "I", 0, "loop_cnt", "" ] 41 | ], 42 | 43 | // button groups 44 | "reg_r": [["magic_code","save_conf"],["bus_cfg_mac","bus_cfg_tx_pre_len"],["dbg_en"],["dbg_raw_msk"], 45 | ["dbg_raw[0]"],["pid_pressure_kp","pid_pressure_out_max"],["set_pressure"],["sen_pressure","loop_cnt"]], 46 | "reg_w": [["magic_code","conf_ver"],["do_reboot"],["save_conf"],["bus_cfg_mac"],["bus_cfg_baud_l","bus_cfg_baud_h"], 47 | ["bus_cfg_filter","bus_cfg_tx_pre_len"],["dbg_en"],["dbg_raw_msk"],["dbg_raw[0]"], 48 | ["pid_pressure_kp","pid_pressure_out_max"],["set_pressure"]], 49 | "less_r": [], 50 | "less_w": [] 51 | }, 52 | 53 | "plot": { 54 | "mask": "dbg_raw_msk", 55 | "plots": [ 56 | { 57 | // "color": ["black", "red", "green"] 58 | "fmt": "B1.fffH", 59 | "label": ["N", "pid target", "i_term", "last_in", "cur_pwm"], 60 | "cal": { 61 | "err": "_d[3].at(-1) - _d[1].at(-1)", // data3 - data1 62 | "in_avg": " \ 63 | let a = 0; \ 64 | for (let i = 0; i < 5; i++) \ 65 | a += _d[3].at(-1-i); \ 66 | return a / 5;" 67 | }, 68 | "fft": { 69 | "sample_rate": 5000, 70 | "size": 4096 71 | } 72 | } 73 | ] 74 | }, 75 | 76 | "iap": { "reboot": 0x0005, "keep_bl": 0x0006 } 77 | } 78 | -------------------------------------------------------------------------------- /html/ctrl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | import { L } from './utils/lang.js' 8 | import { escape_html, date2num, val2hex, dat2str, dat2hex, hex2dat, 9 | read_file, download, readable_size, blob2dat } from './utils/helper.js'; 10 | import { CDWebSocket, CDWebSocketNS } from './utils/cd_ws.js'; 11 | import { Idb } from './utils/idb.js'; 12 | import { csa, alloc_port } from './common.js'; 13 | import { init_reg } from './plugins/reg.js'; 14 | import { init_plot } from './plugins/plot.js'; 15 | import { init_dbg } from './plugins/dbg.js'; 16 | import { init_pic } from './plugins/pic.js'; 17 | import { init_iap } from './plugins/iap.js'; 18 | import { init_export } from './plugins/export.js'; 19 | 20 | 21 | function init_ws() { 22 | let ws_url = `ws://${window.location.hostname}:8910/${csa.arg.tgt}`; 23 | let ws = new WebSocket(ws_url); 24 | 25 | ws.onopen = async function(evt) { 26 | console.log("ws onopen"); 27 | csa.ws_ns.connections['server'] = ws; 28 | 29 | csa.cmd_sock.flush(); 30 | await csa.cmd_sock.sendto({'action': 'get_cfg', 'cfg': csa.arg.cfg}, ['server', 'cfgs']); 31 | let dat = await csa.cmd_sock.recvfrom(2000); 32 | console.log('get_cfg ret', dat[0]); 33 | csa.cfg = dat[0]; 34 | 35 | await alloc_port('clr_all'); 36 | await init_reg(); 37 | await init_dbg(); 38 | await init_plot(); 39 | await init_pic(); 40 | await init_iap(); 41 | await init_export(); 42 | 43 | let port = await alloc_port(); 44 | csa.proxy_sock_info = new CDWebSocket(csa.ws_ns, port); 45 | document.getElementById('dev_read_info').click(); 46 | } 47 | ws.onmessage = async function(evt) { 48 | let dat = await blob2dat(evt.data); 49 | var msg = msgpack.deserialize(dat); 50 | //console.log("Received dat", msg); 51 | if (msg['dst'][1] in csa.ws_ns.sockets) { 52 | let sock = csa.ws_ns.sockets[msg['dst'][1]]; 53 | sock.recv_q.put([msg['dat'], msg['src']]); 54 | } else { 55 | console.log("ws drop msg:", msg); 56 | } 57 | } 58 | ws.onerror = function(evt) { 59 | console.log("ws onerror: ", evt); 60 | document.body.style.backgroundColor = "gray"; 61 | } 62 | ws.onclose = function(evt) { 63 | delete csa.ws_ns.connections['server']; 64 | console.log('ws disconnected'); 65 | document.body.style.backgroundColor = "gray"; 66 | } 67 | } 68 | 69 | 70 | document.getElementById('dev_read_info').onclick = async function() { 71 | let elem = document.getElementById('dev_info'); 72 | elem.style.background = '#D5F5E3'; 73 | elem.innerText = 'Reading ...'; 74 | 75 | csa.cmd_sock.flush(); 76 | await csa.cmd_sock.sendto({'action': 'get'}, ['server', 'dev']); 77 | let dat = await csa.cmd_sock.recvfrom(1000); 78 | if (!dat) { 79 | elem.style.background = '#F5B7B180'; 80 | elem.innerText = 'WebSocket timeout'; 81 | return; 82 | } else if (dat[0] != 'udp' && !dat[0].online) { 83 | elem.style.background = '#F5B7B180'; 84 | elem.innerText = L('Serial disconnected'); 85 | return; 86 | } 87 | 88 | csa.proxy_sock_info.flush(); 89 | await csa.proxy_sock_info.sendto({'dst': [csa.arg.tgt, 0x1], 'dat': new Uint8Array([])}, ['server', 'proxy']); 90 | console.log('read info wait ret'); 91 | let ret = await csa.proxy_sock_info.recvfrom(1000); 92 | console.log('read info ret', ret); 93 | if (ret) { 94 | elem.innerText = `${dat2str(ret[0].dat)}`; 95 | elem.style.background = '#D5F5E360'; 96 | setTimeout(() => { elem.style.background = ''; }, 100); 97 | } else { 98 | elem.innerText = 'Timeout'; 99 | elem.style.background = '#F5B7B180'; 100 | } 101 | }; 102 | 103 | 104 | window.addEventListener('load', async function() { 105 | console.log("load ctrl"); 106 | 107 | // apply translation 108 | for (let tag of ['button', 'span', 'option', 'td']) { 109 | let elems = document.getElementsByTagName(tag); 110 | for (let e of elems) { 111 | e.innerHTML = eval("`" + e.innerHTML + "`"); 112 | if (e.title) 113 | e.title = eval("`" + e.title + "`"); 114 | } 115 | } 116 | 117 | let url_arg = new URLSearchParams(location.search); 118 | 119 | csa.arg.tgt = url_arg.get('tgt') 120 | csa.arg.cfg = url_arg.get('cfg') 121 | csa.arg.name = url_arg.get('name') 122 | if (!csa.arg.tgt || !csa.arg.cfg) { 123 | alert("no tgt or cfg"); 124 | return; 125 | } 126 | document.getElementById('tgt_name').innerText = ` - ${csa.arg.name} < ${csa.arg.tgt} | ${csa.arg.cfg} >`; 127 | 128 | csa.ws_ns = new CDWebSocketNS(`/${csa.arg.tgt}`); 129 | csa.cmd_sock = new CDWebSocket(csa.ws_ns, 'cmd'); 130 | 131 | csa.db = await new Idb(); 132 | init_ws(); 133 | }); 134 | 135 | export { csa }; 136 | 137 | -------------------------------------------------------------------------------- /html/plugins/dbg.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | import { L } from '../utils/lang.js' 8 | import { timestamp, dat2str } from '../utils/helper.js'; 9 | import { CDWebSocket } from '../utils/cd_ws.js'; 10 | import { csa, alloc_port } from '../common.js'; 11 | import { Terminal } from '../libs/xterm-5.6.0-beta.129.js'; 12 | import { WebglAddon } from '../libs/xterm-addon-webgl-0.19.0-beta.129.js'; 13 | import { FitAddon } from '../libs/xterm-addon-fit-0.11.0-beta.129.js'; 14 | import { SearchAddon } from '../libs/xterm-addon-search-0.16.0-beta.129.js'; 15 | 16 | let html = ` 17 |
18 |

Logs

19 |
20 | ${L('Max Len')}: 21 | 22 | | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
`; 30 | 31 | let term = null; 32 | let origin_log = []; 33 | 34 | function write_log(line) { 35 | const buffer = term.buffer.active; 36 | term.write(line); 37 | origin_log.push(line); 38 | if(buffer.viewportY + term.rows >= buffer.length) 39 | term.scrollToBottom(); 40 | } 41 | 42 | function update_max_len() { 43 | let num = Number(document.getElementById('dbg_len').value); 44 | if (num) { 45 | term.options.scrollback = num; 46 | console.log(`dbg set max len: ${num}`); 47 | } 48 | } 49 | 50 | async function dbg_service() { 51 | term = new Terminal({ 52 | convertEol: true, // using '\n' instead of '\r\n' 53 | fontSize: 12 54 | }); 55 | update_max_len(); 56 | document.getElementById('dbg_len').onchange = update_max_len; 57 | 58 | const webgl_addon = new WebglAddon(); 59 | webgl_addon.onContextLoss(e => { 60 | webgl_addon.dispose(); 61 | }); 62 | term.loadAddon(webgl_addon); 63 | const fit_addon = new FitAddon(); 64 | term.loadAddon(fit_addon); 65 | const search_addon = new SearchAddon(); 66 | term.loadAddon(search_addon); 67 | term.open(document.getElementById('dbg_log')); 68 | const observer = new ResizeObserver(() => fit_addon.fit()); 69 | observer.observe(document.getElementById('dbg_log')); 70 | 71 | term.attachCustomKeyEventHandler((e) => { 72 | if (e.ctrlKey && e.code == 'KeyC' && e.type == 'keydown') { 73 | if (term.hasSelection()) { 74 | const selected = term.getSelection(); 75 | navigator.clipboard.writeText(selected); 76 | e.preventDefault(); 77 | return false; 78 | } 79 | } 80 | if (e.code == 'Enter' && e.type == 'keydown') { 81 | term.writeln(''); 82 | origin_log.push('\n'); 83 | return false; 84 | } 85 | if (e.key == "F5") 86 | return false; // allow page refresh with F5 87 | return true; 88 | }); 89 | term.element.addEventListener('wheel', (e) => { 90 | e.preventDefault(); // scroll log without scrolling the page 91 | }); 92 | 93 | document.getElementById('dbg_clear').onclick = () => { 94 | term.clear(); 95 | term.select(0, 0, 0); 96 | }; 97 | document.getElementById('dbg_select_all').onclick = () => { 98 | term.selectAll(); 99 | term.focus(); 100 | }; 101 | document.getElementById('dbg_search_prev').onclick = () => { 102 | const val = document.getElementById('dbg_search').value; 103 | search_addon.findPrevious(val, {caseSensitive: true}); 104 | }; 105 | document.getElementById('dbg_search_next').onclick = () => { 106 | const val = document.getElementById('dbg_search').value; 107 | search_addon.findNext(val, {caseSensitive: true}); 108 | }; 109 | 110 | while (true) { 111 | let dat = await csa.dbg.sock.recvfrom(); 112 | console.log('dbg get:', dat2str(dat[0].dat)); 113 | let elem = document.getElementById('dbg_log'); 114 | let txt = dat2str(dat[0].dat); 115 | write_log(txt); 116 | } 117 | } 118 | 119 | async function init_dbg() { 120 | csa.dbg = {}; 121 | csa.plugins.push('dbg'); 122 | 123 | let port = await alloc_port(9); 124 | console.log(`init_dbg, alloc port: ${port}`); 125 | csa.dbg.sock = new CDWebSocket(csa.ws_ns, port); 126 | 127 | document.head.insertAdjacentHTML('beforeend', ''); 128 | document.getElementsByTagName('section')[0].insertAdjacentHTML('beforeend', html); 129 | dbg_service(); 130 | 131 | csa.dbg.dat_export = () => { return origin_log.join(''); }; 132 | csa.dbg.dat_import = (dat) => { 133 | term.write(dat); 134 | term.scrollToBottom(); 135 | }; 136 | } 137 | 138 | export { init_dbg }; 139 | 140 | -------------------------------------------------------------------------------- /tools/cdg_iap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Software License Agreement (MIT License) 4 | # 5 | # Author: Duke Fong 6 | 7 | """CDBUS IAP Tool 8 | 9 | Args: 10 | --in-file FILE # write FILE to mcu (.hex or .bin format) 11 | --out-file FILE # read FILE from mcu (.bin only) 12 | --addr ADDR # mcu ram address (only for .bin file) 13 | --size SIZE # only needed when read from mcu 14 | --flash-only # do not reboot 15 | --enter-bl # enter bootloader only 16 | 17 | Examples: 18 | 19 | write fw: 20 | ./cdg_iap.py --cfg CFG_FILE --in-file fw.hex 21 | ./cdg_iap.py --cfg CFG_FILE --in-file fw.bin --addr=0x0800c000 22 | 23 | read fw: 24 | ./cdg_iap.py --cfg CFG_FILE --out-file fw.bin --addr=0x0800c000 --size=xxx 25 | 26 | More args refers to: 27 | """ 28 | 29 | import sys, os 30 | import struct 31 | import re 32 | from time import sleep 33 | from intelhex import IntelHex 34 | from cdg_cmd import * 35 | 36 | sub_size = 128 37 | 38 | 39 | def cdg_iap_init(): 40 | global addr, size, in_file, out_file, flash_only, enter_bl 41 | addr = int(csa['args'].get("--addr", dft="0x0800c000"), 0) 42 | size = int(csa['args'].get("--size", dft="0"), 0) 43 | in_file = csa['args'].get("--in-file") 44 | out_file = csa['args'].get("--out-file") 45 | flash_only = csa['args'].get("--flash-only") != None 46 | enter_bl = csa['args'].get("--enter-bl") != None 47 | 48 | if not in_file and not out_file and not enter_bl: 49 | print(__doc__) 50 | exit(-1) 51 | 52 | 53 | def _read_flash(addr, _len): 54 | csa['sock'].sendto(b'\x00' + struct.pack("=0&&e<=127)c(e);else if(e<0&&e>=-32)c(e);else if(e>0&&e<=255)s([204,e]);else if(e>=-128&&e<=127)s([208,e]);else if(e>0&&e<=65535)s([205,e>>>8,e]);else if(e>=-32768&&e<=32767)s([209,e>>>8,e]);else if(e>0&&e<=4294967295)s([206,e>>>24,e>>>16,e>>>8,e]);else if(e>=-2147483648&&e<=2147483647)s([210,e>>>24,e>>>16,e>>>8,e]);else if(e>0&&e<=0x10000000000000000){let t=e/r,n=e%r;s([211,t>>>24,t>>>16,t>>>8,t,n>>>24,n>>>16,n>>>8,n])}else e>=-0x8000000000000000&&e<=0x8000000000000000?(c(211),d(e)):s(e<0?[211,128,0,0,0,0,0,0,0]:[207,255,255,255,255,255,255,255,255]);else i||(n=new ArrayBuffer(8),i=new DataView(n)),i.setFloat64(0,e),c(203),s(new Uint8Array(n))}(e);break;case"string":!function(e){let t=function(e){let t=!0,r=e.length;for(let n=0;n127){t=!1;break}let n=0,i=new Uint8Array(e.length*(t?1:4));for(let t=0;t!==r;t++){let o=e.charCodeAt(t);if(o<128)i[n++]=o;else{if(o<2048)i[n++]=o>>6|192;else{if(o>55295&&o<56320){if(++t>=r)throw new Error("UTF-8 encode: incomplete surrogate pair");let f=e.charCodeAt(t);if(f<56320||f>57343)throw new Error("UTF-8 encode: second surrogate character 0x"+f.toString(16)+" at index "+t+" out of range");o=65536+((1023&o)<<10)+(1023&f),i[n++]=o>>18|240,i[n++]=o>>12&63|128}else i[n++]=o>>12|224;i[n++]=o>>6&63|128}i[n++]=63&o|128}}return t?i:i.subarray(0,n)}(e),r=t.length;r<=31?c(160+r):s(r<=255?[217,r]:r<=65535?[218,r>>>8,r]:[219,r>>>24,r>>>16,r>>>8,r]);s(t)}(e);break;case"object":null===e?l(e):e instanceof Date?function(e){let t=e.getTime()/1e3;if(0===e.getMilliseconds()&&t>=0&&t<4294967296)s([214,255,t>>>24,t>>>16,t>>>8,t]);else if(t>=0&&t<17179869184){let n=1e6*e.getMilliseconds();s([215,255,n>>>22,n>>>14,n>>>6,n<<2>>>0|t/r,t>>>24,t>>>16,t>>>8,t])}else{let r=1e6*e.getMilliseconds();s([199,12,255,r>>>24,r>>>16,r>>>8,r]),d(t)}}(e):Array.isArray(e)?u(e):e instanceof Uint8Array||e instanceof Uint8ClampedArray?function(e){let t=e.length;s(t<=15?[196,t]:t<=65535?[197,t>>>8,t]:[198,t>>>24,t>>>16,t>>>8,t]);s(e)}(e):e instanceof Int8Array||e instanceof Int16Array||e instanceof Uint16Array||e instanceof Int32Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array?u(e):function(e){let t=0;for(let r in e)void 0!==e[r]&&t++;t<=15?c(128+t):s(t<=65535?[222,t>>>8,t]:[223,t>>>24,t>>>16,t>>>8,t]);for(let t in e){let r=e[t];void 0!==r&&(a(t),a(r))}}(e);break;default:if(o||!t||!t.invalidTypeReplacement)throw new Error("Invalid argument type: The type '"+typeof e+"' cannot be serialized.");"function"==typeof t.invalidTypeReplacement?a(t.invalidTypeReplacement(e),!0):a(t.invalidTypeReplacement,!0)}}function l(e){c(192)}function u(e){let t=e.length;t<=15?c(144+t):s(t<=65535?[220,t>>>8,t]:[221,t>>>24,t>>>16,t>>>8,t]);for(let r=0;r=0?(t=e/r,n=e%r):(e++,t=~(t=Math.abs(e)/r),n=~(n=Math.abs(e)%r)),s([t>>>24,t>>>16,t>>>8,t,n>>>24,n>>>16,n>>>8,n])}}function t(e,t){const r=4294967296;let n,i=0;if(e instanceof ArrayBuffer&&(e=new Uint8Array(e)),"object"!=typeof e||void 0===e.length)throw new Error("Invalid argument type: Expected a byte array (Array or Uint8Array) to deserialize.");if(!e.length)throw new Error("Invalid argument: The byte array to deserialize is empty.");if(e instanceof Uint8Array||(e=new Uint8Array(e)),t&&t.multiple)for(n=[];i=0&&t<=127)return t;if(t>=128&&t<=143)return c(t-128);if(t>=144&&t<=159)return s(t-144);if(t>=160&&t<=191)return d(t-160);if(192===t)return null;if(193===t)throw new Error("Invalid byte code 0xc1 found.");if(194===t)return!1;if(195===t)return!0;if(196===t)return u(-1,1);if(197===t)return u(-1,2);if(198===t)return u(-1,4);if(199===t)return y(-1,1);if(200===t)return y(-1,2);if(201===t)return y(-1,4);if(202===t)return l(4);if(203===t)return l(8);if(204===t)return a(1);if(205===t)return a(2);if(206===t)return a(4);if(207===t)return a(8);if(208===t)return f(1);if(209===t)return f(2);if(210===t)return f(4);if(211===t)return f(8);if(212===t)return y(1);if(213===t)return y(2);if(214===t)return y(4);if(215===t)return y(8);if(216===t)return y(16);if(217===t)return d(-1,1);if(218===t)return d(-1,2);if(219===t)return d(-1,4);if(220===t)return s(-1,2);if(221===t)return s(-1,4);if(222===t)return c(-1,2);if(223===t)return c(-1,4);if(t>=224&&t<=255)return t-256;throw console.debug("msgpack array:",e),new Error("Invalid byte value '"+t+"' at index "+(i-1)+" in the MessagePack binary data (length "+e.length+"): Expecting a range of 0 to 255. This is not a byte array.")}function f(t){let r=0,n=!0;for(;t-- >0;)if(n){let t=e[i++];r+=127&t,128&t&&(r-=128),n=!1}else r*=256,r+=e[i++];return r}function a(t){let r=0;for(;t-- >0;)r*=256,r+=e[i++];return r}function l(t){let r=new DataView(e.buffer,i+e.byteOffset,t);return i+=t,4===t?r.getFloat32(0,!1):8===t?r.getFloat64(0,!1):void 0}function u(t,r){t<0&&(t=a(r));let n=e.subarray(i,i+t);return i+=t,n}function c(e,t){e<0&&(e=a(t));let r={};for(;e-- >0;){r[o()]=o()}return r}function s(e,t){e<0&&(e=a(t));let r=[];for(;e-- >0;)r.push(o());return r}function d(t,r){t<0&&(t=a(r));let n=i;return i+=t,function(e,t,r){let n=t,i="";r+=t;for(;n127)if(t>191&&t<224){if(n>=r)throw new Error("UTF-8 decode: incomplete 2-byte sequence");t=(31&t)<<6|63&e[n++]}else if(t>223&&t<240){if(n+1>=r)throw new Error("UTF-8 decode: incomplete 3-byte sequence");t=(15&t)<<12|(63&e[n++])<<6|63&e[n++]}else{if(!(t>239&&t<248))throw new Error("UTF-8 decode: unknown multibyte start 0x"+t.toString(16)+" at index "+(n-1));if(n+2>=r)throw new Error("UTF-8 decode: incomplete 4-byte sequence");t=(7&t)<<18|(63&e[n++])<<12|(63&e[n++])<<6|63&e[n++]}if(t<=65535)i+=String.fromCharCode(t);else{if(!(t<=1114111))throw new Error("UTF-8 decode: code point 0x"+t.toString(16)+" exceeds UTF-16 reach");t-=65536,i+=String.fromCharCode(t>>10|55296),i+=String.fromCharCode(1023&t|56320)}}return i}(e,n,t)}function y(e,t){e<0&&(e=a(t));let n=a(1),o=u(e);switch(n){case 255:return function(e){if(4===e.length){let t=(e[0]<<24>>>0)+(e[1]<<16>>>0)+(e[2]<<8>>>0)+e[3];return new Date(1e3*t)}if(8===e.length){let t=(e[0]<<22>>>0)+(e[1]<<14>>>0)+(e[2]<<6>>>0)+(e[3]>>>2),n=(3&e[3])*r+(e[4]<<24>>>0)+(e[5]<<16>>>0)+(e[6]<<8>>>0)+e[7];return new Date(1e3*n+t/1e6)}if(12===e.length){let t=(e[0]<<24>>>0)+(e[1]<<16>>>0)+(e[2]<<8>>>0)+e[3];i-=8;let r=f(8);return new Date(1e3*r+t/1e6)}throw new Error("Invalid data length for a date value.")}(o)}return{type:n,data:o}}}let r={serialize:e,deserialize:t,encode:e,decode:t};"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=r:window[window.msgpackJsName||"msgpack"]=r}(); 2 | //# sourceMappingURL=msgpack.min.js.map -------------------------------------------------------------------------------- /html/libs/xterm-5.6.0-beta.129.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 The xterm.js authors. All rights reserved. 3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 4 | * https://github.com/chjj/term.js 5 | * @license MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | * Originally forked from (with the author's permission): 26 | * Fabrice Bellard's javascript vt100 for jslinux: 27 | * http://bellard.org/jslinux/ 28 | * Copyright (c) 2011 Fabrice Bellard 29 | * The original design remains. The terminal itself 30 | * has been extended to include xterm CSI codes, among 31 | * other features. 32 | */ 33 | 34 | /** 35 | * Default styles for xterm.js 36 | */ 37 | 38 | .xterm { 39 | cursor: text; 40 | position: relative; 41 | user-select: none; 42 | -ms-user-select: none; 43 | -webkit-user-select: none; 44 | } 45 | 46 | .xterm.focus, 47 | .xterm:focus { 48 | outline: none; 49 | } 50 | 51 | .xterm .xterm-helpers { 52 | position: absolute; 53 | top: 0; 54 | /** 55 | * The z-index of the helpers must be higher than the canvases in order for 56 | * IMEs to appear on top. 57 | */ 58 | z-index: 5; 59 | } 60 | 61 | .xterm .xterm-helper-textarea { 62 | padding: 0; 63 | border: 0; 64 | margin: 0; 65 | /* Move textarea out of the screen to the far left, so that the cursor is not visible */ 66 | position: absolute; 67 | opacity: 0; 68 | left: -9999em; 69 | top: 0; 70 | width: 0; 71 | height: 0; 72 | z-index: -5; 73 | /** Prevent wrapping so the IME appears against the textarea at the correct position */ 74 | white-space: nowrap; 75 | overflow: hidden; 76 | resize: none; 77 | } 78 | 79 | .xterm .composition-view { 80 | /* TODO: Composition position got messed up somewhere */ 81 | background: #000; 82 | color: #FFF; 83 | display: none; 84 | position: absolute; 85 | white-space: nowrap; 86 | z-index: 1; 87 | } 88 | 89 | .xterm .composition-view.active { 90 | display: block; 91 | } 92 | 93 | .xterm .xterm-viewport { 94 | /* On OS X this is required in order for the scroll bar to appear fully opaque */ 95 | background-color: #000; 96 | overflow-y: scroll; 97 | cursor: default; 98 | position: absolute; 99 | right: 0; 100 | left: 0; 101 | top: 0; 102 | bottom: 0; 103 | } 104 | 105 | .xterm .xterm-screen { 106 | position: relative; 107 | } 108 | 109 | .xterm .xterm-screen canvas { 110 | position: absolute; 111 | left: 0; 112 | top: 0; 113 | } 114 | 115 | .xterm-char-measure-element { 116 | display: inline-block; 117 | visibility: hidden; 118 | position: absolute; 119 | top: 0; 120 | left: -9999em; 121 | line-height: normal; 122 | } 123 | 124 | .xterm.enable-mouse-events { 125 | /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ 126 | cursor: default; 127 | } 128 | 129 | .xterm.xterm-cursor-pointer, 130 | .xterm .xterm-cursor-pointer { 131 | cursor: pointer; 132 | } 133 | 134 | .xterm.column-select.focus { 135 | /* Column selection mode */ 136 | cursor: crosshair; 137 | } 138 | 139 | .xterm .xterm-accessibility:not(.debug), 140 | .xterm .xterm-message { 141 | position: absolute; 142 | left: 0; 143 | top: 0; 144 | bottom: 0; 145 | right: 0; 146 | z-index: 10; 147 | color: transparent; 148 | pointer-events: none; 149 | } 150 | 151 | .xterm .xterm-accessibility-tree:not(.debug) *::selection { 152 | color: transparent; 153 | } 154 | 155 | .xterm .xterm-accessibility-tree { 156 | font-family: monospace; 157 | user-select: text; 158 | white-space: pre; 159 | } 160 | 161 | .xterm .xterm-accessibility-tree > div { 162 | transform-origin: left; 163 | width: fit-content; 164 | } 165 | 166 | .xterm .live-region { 167 | position: absolute; 168 | left: -9999px; 169 | width: 1px; 170 | height: 1px; 171 | overflow: hidden; 172 | } 173 | 174 | .xterm-dim { 175 | /* Dim should not apply to background, so the opacity of the foreground color is applied 176 | * explicitly in the generated class and reset to 1 here */ 177 | opacity: 1 !important; 178 | } 179 | 180 | .xterm-underline-1 { text-decoration: underline; } 181 | .xterm-underline-2 { text-decoration: double underline; } 182 | .xterm-underline-3 { text-decoration: wavy underline; } 183 | .xterm-underline-4 { text-decoration: dotted underline; } 184 | .xterm-underline-5 { text-decoration: dashed underline; } 185 | 186 | .xterm-overline { 187 | text-decoration: overline; 188 | } 189 | 190 | .xterm-overline.xterm-underline-1 { text-decoration: overline underline; } 191 | .xterm-overline.xterm-underline-2 { text-decoration: overline double underline; } 192 | .xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; } 193 | .xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; } 194 | .xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; } 195 | 196 | .xterm-strikethrough { 197 | text-decoration: line-through; 198 | } 199 | 200 | .xterm-screen .xterm-decoration-container .xterm-decoration { 201 | z-index: 6; 202 | position: absolute; 203 | } 204 | 205 | .xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer { 206 | z-index: 7; 207 | } 208 | 209 | .xterm-decoration-overview-ruler { 210 | z-index: 8; 211 | position: absolute; 212 | top: 0; 213 | right: 0; 214 | pointer-events: none; 215 | } 216 | 217 | .xterm-decoration-top { 218 | z-index: 2; 219 | position: relative; 220 | } 221 | 222 | 223 | 224 | /* Derived from vs/base/browser/ui/scrollbar/media/scrollbar.css */ 225 | 226 | /* xterm.js customization: Override xterm's cursor style */ 227 | .xterm .xterm-scrollable-element > .scrollbar { 228 | cursor: default; 229 | } 230 | 231 | /* Arrows */ 232 | .xterm .xterm-scrollable-element > .scrollbar > .scra { 233 | cursor: pointer; 234 | font-size: 11px !important; 235 | } 236 | 237 | .xterm .xterm-scrollable-element > .visible { 238 | opacity: 1; 239 | 240 | /* Background rule added for IE9 - to allow clicks on dom node */ 241 | background:rgba(0,0,0,0); 242 | 243 | transition: opacity 100ms linear; 244 | /* In front of peek view */ 245 | z-index: 11; 246 | } 247 | .xterm .xterm-scrollable-element > .invisible { 248 | opacity: 0; 249 | pointer-events: none; 250 | } 251 | .xterm .xterm-scrollable-element > .invisible.fade { 252 | transition: opacity 800ms linear; 253 | } 254 | 255 | /* Scrollable Content Inset Shadow */ 256 | .xterm .xterm-scrollable-element > .shadow { 257 | position: absolute; 258 | display: none; 259 | } 260 | .xterm .xterm-scrollable-element > .shadow.top { 261 | display: block; 262 | top: 0; 263 | left: 3px; 264 | height: 3px; 265 | width: 100%; 266 | box-shadow: var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset; 267 | } 268 | .xterm .xterm-scrollable-element > .shadow.left { 269 | display: block; 270 | top: 3px; 271 | left: 0; 272 | height: 100%; 273 | width: 3px; 274 | box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset; 275 | } 276 | .xterm .xterm-scrollable-element > .shadow.top-left-corner { 277 | display: block; 278 | top: 0; 279 | left: 0; 280 | height: 3px; 281 | width: 3px; 282 | } 283 | .xterm .xterm-scrollable-element > .shadow.top.left { 284 | box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset; 285 | } 286 | -------------------------------------------------------------------------------- /html/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | import { L } from './utils/lang.js' 8 | import { escape_html, date2num, timestamp, val2hex, dat2str, dat2hex, hex2dat, 9 | read_file, download, readable_size, blob2dat } from './utils/helper.js'; 10 | import { CDWebSocket, CDWebSocketNS } from './utils/cd_ws.js'; 11 | import { Idb } from './utils/idb.js'; 12 | import { csa, alloc_port } from './common.js'; 13 | import { init_dbg } from './plugins/dbg.js'; 14 | 15 | 16 | csa.ws_ns = new CDWebSocketNS('/'); 17 | csa.cmd_sock = new CDWebSocket(csa.ws_ns, 'cmd'); 18 | let cfgs = null; 19 | const dev_max = 100; 20 | 21 | 22 | async function auto_hide() { 23 | let devs = []; 24 | for (let n = 0; n < dev_max; n++) { 25 | const dev = { 26 | tgt: document.getElementById(`cfg${n}.tgt`).value, 27 | cfg: document.getElementById(`cfg${n}.cfg`).value, 28 | name: document.getElementById(`cfg${n}.name`).value, 29 | }; 30 | devs.push(dev); 31 | } 32 | let actived = 7; 33 | for (let n = 0; n < dev_max; n++) { 34 | if (devs[n].name && n >= actived) 35 | actived = n + 1; 36 | } 37 | devs = devs.slice(0, actived); 38 | await csa.db.set('tmp', '_index_/dev.list', devs); 39 | 40 | console.log("auto_hide:", devs); 41 | for (let i = 0; i < dev_max; i++) 42 | document.getElementById(`device_grp${i}`).style.display = i <= actived ? '' : 'none'; 43 | } 44 | 45 | async function init_serial_cfg() { 46 | let ser_cfg = await csa.db.get('tmp', '_index_/ser.cfg'); 47 | let port = document.getElementById('dev_port'); 48 | let baud = document.getElementById('dev_baud'); 49 | 50 | if (ser_cfg) { 51 | port.value = ser_cfg.port; 52 | baud.value = ser_cfg.baud; 53 | } 54 | 55 | port.onchange = baud.onchange = async () => { 56 | await csa.db.set('tmp', '_index_/ser.cfg', { 57 | port: port.value, 58 | baud: baud.value 59 | }); 60 | }; 61 | } 62 | 63 | async function init_cfg_list() { 64 | let sel_ops = ''; 65 | for (let op of cfgs) 66 | sel_ops += ``; 67 | let list = document.getElementById('cfg_list'); 68 | 69 | let devs = await csa.db.get('tmp', '_index_/dev.list'); 70 | console.log("init get devs:", devs); 71 | if (!devs) 72 | devs = []; 73 | for (let i = 0; i < dev_max; i++) { 74 | let tgt = (devs && devs[i]) ? devs[i].tgt : `00:00:fe`; 75 | let cfg = (devs && devs[i]) ? devs[i].cfg : ''; 76 | let name = (devs && devs[i]) ? devs[i].name : ''; 77 | let html = ` 78 |
79 |
80 | 81 | 82 | 83 | 84 |
85 |
86 | `; 87 | 88 | list.insertAdjacentHTML('beforeend', html); 89 | document.getElementById(`cfg${i}.cfg`).value = `${cfg}`; 90 | 91 | document.getElementById(`cfg${i}.btn`).onclick = async () => { 92 | let t = document.getElementById(`cfg${i}.tgt`).value; 93 | let c = document.getElementById(`cfg${i}.cfg`).value; 94 | let n = document.getElementById(`cfg${i}.name`).value; 95 | console.log(`t: ${t}, c: ${c}`); 96 | if (!t || !c || !n) { 97 | alert('Empty not allowed'); 98 | return; 99 | } 100 | window.open(`ctrl.html?tgt=${t}&cfg=${c}&name=${n}`, "_blank"); 101 | }; 102 | 103 | document.getElementById(`cfg${i}.name`).onchange = 104 | document.getElementById(`cfg${i}.tgt`).onchange = 105 | document.getElementById(`cfg${i}.cfg`).onchange = async () => { await auto_hide(); }; 106 | } 107 | await auto_hide(); 108 | } 109 | 110 | 111 | function init_ws() { 112 | let ws_url = 'ws://' + window.location.hostname + ':8910'; 113 | let ws = new WebSocket(ws_url); 114 | 115 | ws.onopen = async function(evt) { 116 | console.log("ws onopen"); 117 | csa.ws_ns.connections['server'] = ws; 118 | 119 | csa.cmd_sock.flush(); 120 | await csa.cmd_sock.sendto({'action': 'get_cfgs'}, ['server', 'cfgs']); 121 | let dat = await csa.cmd_sock.recvfrom(2000); 122 | console.log('get_cfgs ret', dat); 123 | cfgs = dat[0]; 124 | 125 | await alloc_port('clr_all'); 126 | await init_dbg(); 127 | 128 | await init_cfg_list(); 129 | await init_serial_cfg(); 130 | await document.getElementById('btn_dev_get').onclick(); 131 | } 132 | ws.onmessage = async function(evt) { 133 | let dat = await blob2dat(evt.data); 134 | var msg = msgpack.deserialize(dat); 135 | //console.log("Received dat", msg); 136 | var sock = csa.ws_ns.sockets[msg['dst'][1]]; 137 | sock.recv_q.put([msg['dat'], msg['src']]); 138 | } 139 | ws.onerror = function(evt) { 140 | console.log("ws onerror: ", evt); 141 | document.body.style.backgroundColor = "gray"; 142 | } 143 | ws.onclose = function(evt) { 144 | delete csa.ws_ns.connections['server']; 145 | console.log('ws disconnected'); 146 | document.body.style.backgroundColor = "gray"; 147 | } 148 | } 149 | 150 | 151 | document.getElementById('btn_dev_get').onclick = async function() { 152 | console.log('start get'); 153 | let status = document.getElementById('dev_status'); 154 | let list = document.getElementById('dev_list'); 155 | document.getElementById('btn_dev_get').disabled = true; 156 | status.style.background = list.style.background = '#D5F5E3'; 157 | 158 | csa.cmd_sock.flush(); 159 | await csa.cmd_sock.sendto({'action': 'get'}, ['server', 'dev']); 160 | let dat = await csa.cmd_sock.recvfrom(1000); 161 | console.log('btn_dev_get ret', dat); 162 | if (dat[0] == 'udp') { 163 | console.log('udp mode!'); 164 | document.getElementById('dev_ctrl_hide').style.display = 'none'; 165 | return; 166 | } 167 | status.innerHTML = `${dat[0].port ? dat[0].port : 'None'} | ${dat[0].online ? L('Online') : L('Offline')} ` + 168 | `(local net: 0x${val2hex(dat[0].net,2)} mac: 0x${val2hex(dat[0].mac,2)})`; 169 | list.innerHTML = ''; 170 | let ports = dat[0].ports; 171 | 172 | if (ports) { 173 | for (let i = 0; i < ports.length; i++) { // escape 174 | let port = ports[i]; 175 | let html = `
  • ${port}
  • `; 176 | list.insertAdjacentHTML('beforeend', html); 177 | //list.lastElementChild.getElementsByTagName("button")[0].onclick = async function() { }; 178 | } 179 | } 180 | status.style.background = list.style.background = '#D5F5E360'; 181 | setTimeout(() => { status.style.background = list.style.background = ''; }, 100); 182 | document.getElementById('btn_dev_get').disabled = false; 183 | }; 184 | 185 | document.getElementById('btn_dev_open').onclick = async function() { 186 | console.log('start open'); 187 | let port = document.getElementById('dev_port').value; 188 | let baud = parseInt(document.getElementById('dev_baud').value); 189 | if (!port || !baud) { 190 | alert('Empty not allowed'); 191 | return; 192 | } 193 | document.getElementById('btn_dev_open').disabled = true; 194 | csa.cmd_sock.flush(); 195 | await csa.cmd_sock.sendto({'action': 'open', 'port': port, 'baud': baud}, ['server', 'dev']); 196 | let dat = await csa.cmd_sock.recvfrom(1000); 197 | console.log('btn_dev_open ret', dat); 198 | await document.getElementById('btn_dev_get').onclick(); 199 | document.getElementById('btn_dev_open').disabled = false; 200 | }; 201 | 202 | document.getElementById('btn_dev_close').onclick = async function() { 203 | console.log('start close'); 204 | document.getElementById('btn_dev_close').disabled = true; 205 | csa.cmd_sock.flush(); 206 | await csa.cmd_sock.sendto({'action': 'close'}, ['server', 'dev']); 207 | let dat = await csa.cmd_sock.recvfrom(1000); 208 | console.log('btn_dev_close ret', dat); 209 | await document.getElementById('btn_dev_get').onclick(); 210 | document.getElementById('btn_dev_close').disabled = false; 211 | }; 212 | 213 | window.addEventListener('load', async function() { 214 | console.log("load app"); 215 | 216 | // apply translation 217 | for (let tag of ['button', 'span', 'option', 'td']) { 218 | let elems = document.getElementsByTagName(tag); 219 | for (let e of elems) { 220 | e.innerHTML = eval("`" + e.innerHTML + "`"); 221 | if (e.title) 222 | e.title = eval("`" + e.title + "`"); 223 | } 224 | } 225 | 226 | csa.db = await new Idb(); 227 | init_ws(); 228 | }); 229 | 230 | -------------------------------------------------------------------------------- /html/plugins/plot_zoom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | let shift_key = false; 8 | let ctrl_key = false; 9 | 10 | window.addEventListener('keydown', function(e) { 11 | if (e.keyCode == 16) { 12 | shift_key = true; 13 | ctrl_key = false; 14 | } else if (e.keyCode == 17) { 15 | shift_key = false; 16 | ctrl_key = true; 17 | } 18 | }); 19 | window.addEventListener('keyup', function(e) { 20 | if (e.keyCode == 16 || e.keyCode == 17) 21 | shift_key = ctrl_key = false; 22 | }); 23 | 24 | 25 | function wheelZoomPlugin(opts) { 26 | let factor = opts.factor || 0.75; 27 | 28 | let xMin, xMax, yMin, yMax, xRange, yRange; 29 | 30 | function clamp(nRange, nMin, nMax, fRange, fMin, fMax) { 31 | if (nRange > fRange) { 32 | nMin = fMin; 33 | nMax = fMax; 34 | } 35 | else if (nMin < fMin) { 36 | nMin = fMin; 37 | nMax = fMin + nRange; 38 | } 39 | else if (nMax > fMax) { 40 | nMax = fMax; 41 | nMin = fMax - nRange; 42 | } 43 | 44 | return [nMin, nMax]; 45 | } 46 | 47 | return { 48 | hooks: { 49 | ready: u => { 50 | xMin = u.scales.x.min; 51 | xMax = u.scales.x.max; 52 | yMin = u.scales.y.min; 53 | yMax = u.scales.y.max; 54 | 55 | xRange = xMax - xMin; 56 | yRange = yMax - yMin; 57 | 58 | let plot = u.root.querySelector(".u-over"); 59 | //let rect = plot.getBoundingClientRect(); 60 | 61 | // wheel drag pan 62 | plot.addEventListener("mousedown", e => { 63 | if (e.button == 0 || e.button == 1) { 64 | // plot.style.cursor = "move"; 65 | e.preventDefault(); 66 | 67 | let left0 = e.clientX; 68 | let top0 = e.clientY; 69 | 70 | let scXMin0 = u.scales.x.min; 71 | let scXMax0 = u.scales.x.max; 72 | let scYMin0 = u.scales.y.min; 73 | let scYMax0 = u.scales.y.max; 74 | 75 | let xUnitsPerPx = u.posToVal(1, 'x') - u.posToVal(0, 'x'); 76 | let yUnitsPerPx = u.posToVal(1, 'y') - u.posToVal(0, 'y'); 77 | 78 | function onmove(e) { 79 | e.preventDefault(); 80 | 81 | let left1 = e.clientX; 82 | let top1 = e.clientY; 83 | 84 | let dx = xUnitsPerPx * (left1 - left0); 85 | let dy = yUnitsPerPx * (top1 - top0); 86 | 87 | u.batch(() => { 88 | u.setScale("x", { 89 | min: scXMin0 - dx, 90 | max: scXMax0 - dx, 91 | }); 92 | 93 | u.setScale("y", { 94 | min: scYMin0 - dy, 95 | max: scYMax0 - dy, 96 | }); 97 | }); 98 | } 99 | 100 | function onup(e) { 101 | document.removeEventListener("mousemove", onmove); 102 | document.removeEventListener("mouseup", onup); 103 | } 104 | 105 | document.addEventListener("mousemove", onmove); 106 | document.addEventListener("mouseup", onup); 107 | } 108 | }); 109 | 110 | // wheel scroll zoom 111 | plot.addEventListener("wheel", e => { 112 | e.preventDefault(); 113 | 114 | let {left, top} = u.cursor; 115 | let rect = plot.getBoundingClientRect(); 116 | 117 | let leftPct = left/rect.width; 118 | let btmPct = 1 - top/rect.height; 119 | let xVal = u.posToVal(left, "x"); 120 | let yVal = u.posToVal(top, "y"); 121 | let oxRange = u.scales.x.max - u.scales.x.min; 122 | let oyRange = u.scales.y.max - u.scales.y.min; 123 | 124 | let factor_x = shift_key ? 1.0 : factor; 125 | let factor_y = ctrl_key ? 1.0 : factor; 126 | 127 | let nxRange = e.deltaY < 0 ? oxRange * factor_x : oxRange / factor_x; 128 | let nxMin = xVal - leftPct * nxRange; 129 | let nxMax = nxMin + nxRange; 130 | //[nxMin, nxMax] = clamp(nxRange, nxMin, nxMax, xRange, xMin, xMax); 131 | 132 | let nyRange = e.deltaY < 0 ? oyRange * factor_y : oyRange / factor_y; 133 | let nyMin = yVal - btmPct * nyRange; 134 | let nyMax = nyMin + nyRange; 135 | //[nyMin, nyMax] = clamp(nyRange, nyMin, nyMax, yRange, yMin, yMax); 136 | 137 | u.batch(() => { 138 | u.setScale("x", { 139 | min: nxMin, 140 | max: nxMax, 141 | }); 142 | 143 | u.setScale("y", { 144 | min: nyMin, 145 | max: nyMax, 146 | }); 147 | }); 148 | }); 149 | } 150 | } 151 | }; 152 | } 153 | 154 | function touchZoomPlugin(opts) { 155 | function init(u, opts, data) { 156 | let plot = u.root.querySelector(".u-over"); 157 | let rect, oxRange, oyRange, xVal, yVal; 158 | let fr = {x: 0, y: 0, dx: 0, dy: 0}; 159 | let to = {x: 0, y: 0, dx: 0, dy: 0}; 160 | 161 | function storePos(t, e) { 162 | let ts = e.touches; 163 | 164 | let t0 = ts[0]; 165 | let t0x = t0.clientX - rect.left; 166 | let t0y = t0.clientY - rect.top; 167 | 168 | if (ts.length == 1) { 169 | t.x = t0x; 170 | t.y = t0y; 171 | t.d = t.dx = t.dy = 1; 172 | } 173 | else { 174 | let t1 = e.touches[1]; 175 | let t1x = t1.clientX - rect.left; 176 | let t1y = t1.clientY - rect.top; 177 | 178 | let xMin = Math.min(t0x, t1x); 179 | let yMin = Math.min(t0y, t1y); 180 | let xMax = Math.max(t0x, t1x); 181 | let yMax = Math.max(t0y, t1y); 182 | 183 | // midpts 184 | t.y = (yMin+yMax)/2; 185 | t.x = (xMin+xMax)/2; 186 | 187 | t.dx = xMax - xMin; 188 | t.dy = yMax - yMin; 189 | 190 | // dist 191 | t.d = Math.sqrt(t.dx * t.dx + t.dy * t.dy); 192 | } 193 | } 194 | 195 | let rafPending = false; 196 | 197 | function zoom() { 198 | rafPending = false; 199 | 200 | let left = to.x; 201 | let top = to.y; 202 | 203 | // non-uniform scaling 204 | let xFactor = fr.dx / to.dx; 205 | let yFactor = fr.dy / to.dy; 206 | 207 | // uniform x/y scaling 208 | //let xFactor = fr.d / to.d; 209 | //let yFactor = fr.d / to.d; 210 | 211 | let leftPct = left/rect.width; 212 | let btmPct = 1 - top/rect.height; 213 | 214 | let nxRange = oxRange * xFactor; 215 | let nxMin = xVal - leftPct * nxRange; 216 | let nxMax = nxMin + nxRange; 217 | 218 | let nyRange = oyRange * yFactor; 219 | let nyMin = yVal - btmPct * nyRange; 220 | let nyMax = nyMin + nyRange; 221 | 222 | u.batch(() => { 223 | u.setScale("x", { 224 | min: nxMin, 225 | max: nxMax, 226 | }); 227 | 228 | u.setScale("y", { 229 | min: nyMin, 230 | max: nyMax, 231 | }); 232 | }); 233 | } 234 | 235 | function touchmove(e) { 236 | storePos(to, e); 237 | 238 | if (!rafPending) { 239 | rafPending = true; 240 | requestAnimationFrame(zoom); 241 | } 242 | } 243 | 244 | plot.addEventListener("touchstart", function(e) { 245 | e.preventDefault(); 246 | rect = plot.getBoundingClientRect(); 247 | 248 | storePos(fr, e); 249 | 250 | oxRange = u.scales.x.max - u.scales.x.min; 251 | oyRange = u.scales.y.max - u.scales.y.min; 252 | 253 | let left = fr.x; 254 | let top = fr.y; 255 | 256 | xVal = u.posToVal(left, "x"); 257 | yVal = u.posToVal(top, "y"); 258 | 259 | document.addEventListener("touchmove", touchmove, {passive: true}); 260 | }); 261 | 262 | plot.addEventListener("touchend", function(e) { 263 | document.removeEventListener("touchmove", touchmove, {passive: true}); 264 | }); 265 | } 266 | 267 | return { 268 | hooks: { 269 | init 270 | } 271 | }; 272 | } 273 | 274 | 275 | export { 276 | wheelZoomPlugin, touchZoomPlugin 277 | }; 278 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Software License Agreement (MIT License) 4 | # 5 | # Author: Duke Fong 6 | 7 | """CDBUS GUI Tool 8 | 9 | Args: 10 | --help | -h # this help message 11 | --verbose | -v # debug level: verbose 12 | --debug | -d # debug level: debug 13 | --local-net LOCAL_NET # default: 0 14 | --local-mac LOCAL_MAC # default: 0 15 | """ 16 | 17 | import os, sys, re 18 | import _thread 19 | import time, datetime 20 | import copy, json5 21 | import asyncio, aiohttp 22 | import websockets 23 | from time import sleep 24 | from cd_ws import CDWebSocket, CDWebSocketNS 25 | from web_serve import ws_ns, start_web 26 | 27 | sys.path.append(os.path.join(os.path.dirname(__file__), 'pycdnet')) 28 | 29 | from cdnet.utils.log import * 30 | from cdnet.utils.cd_args import CdArgs 31 | from cdnet.dev.cdbus_serial import CDBusSerial 32 | from cdnet.utils.serial_get_port import get_ports 33 | from cdnet.dispatch import * 34 | from cdnet.parser import * 35 | 36 | csa = { 37 | 'async_loop': None, 38 | 'dev': None, # serial device 39 | 'net': 0x00, # local net 40 | 'mac': 0x00, # local mac 41 | 'proxy': None, # cdbus frame proxy socket 42 | 'cfgs': [], # config list 43 | 'palloc': {}, # ports alloc, url_path: [] 44 | } 45 | 46 | args = CdArgs() 47 | if args.get("--help", "-h") != None: 48 | print(__doc__) 49 | exit() 50 | 51 | csa['net'] = int(args.get("--local-net", dft="0x00"), 0) 52 | csa['mac'] = int(args.get("--local-mac", dft="0x00"), 0) 53 | 54 | if args.get("--verbose", "-v") != None: 55 | logger_init(logging.VERBOSE) 56 | elif args.get("--debug", "-d") != None: 57 | logger_init(logging.DEBUG) 58 | else: 59 | logger_init(logging.INFO) 60 | 61 | logging.getLogger('websockets').setLevel(logging.WARNING) 62 | logger = logging.getLogger(f'cdgui') 63 | 64 | 65 | # proxy to html: ('/x0:00:dev_mac', host_port) <- ('server', 'proxy'): { 'src': src, 'dat': payloads } 66 | async def proxy_rx_rpt(rx): 67 | src, dst, dat = rx 68 | logger.debug(f'rx_rpt: src: {src}, dst: {dst}, dat: {dat}') 69 | if dst[1] == 0x9 or src[1] == 0x1: 70 | time_str = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3].encode() 71 | # dbg and dev_info msg also send to index.html 72 | dat4idx = re.sub(b'\n(?!$)', b'\n' + b' ' * 25, dat) # except the end '\n' 73 | dat4idx = time_str + b' [' + src[0].encode() + b']' + b': ' + dat4idx 74 | if src[1] == 0x1: 75 | dat4idx += b'\n' 76 | await csa['proxy'].sendto({'src': src, 'dat': dat4idx}, (f'/', 0x9)) 77 | if dst[1] == 0x9: 78 | dat = re.sub(b'\n(?!$)', b'\n' + b' ' * 14, dat) 79 | dat = time_str + b': ' + dat 80 | ret = await csa['proxy'].sendto({'src': src, 'dat': dat}, (f'/{src[0]}', dst[1])) 81 | if ret: 82 | logger.warning(f'rx_rpt err: {ret}: /{src[0]}:{dst[1]}, {dat}') 83 | 84 | def proxy_rx(): 85 | logger.info('start proxy_rx') 86 | while True: 87 | if not csa['dev']: 88 | sleep(0.5) 89 | continue 90 | frame = None 91 | try: 92 | frame = csa['dev'].recv(timeout=0.5) 93 | if frame: 94 | if frame[3] & 0x80: 95 | rx = cdnet_l1.from_frame(frame, csa['net']) 96 | logger.log(logging.VERBOSE, f'proxy_rx l1: {frame}') 97 | else: 98 | rx = cdnet_l0.from_frame(frame, csa['net']) 99 | logger.log(logging.VERBOSE, f'proxy_rx l0: {frame}') 100 | asyncio.run_coroutine_threadsafe(proxy_rx_rpt(rx), csa['async_loop']).result() 101 | except Exception as err: 102 | logger.warning(f'proxy_rx: err: {err}', frame) 103 | 104 | _thread.start_new_thread(proxy_rx, ()) 105 | 106 | # proxy to dev, ('/x0:00:dev_mac', host_port) -> ('server', 'proxy'): { 'dst': dst, 'dat': payloads } 107 | async def cdbus_proxy_service(): 108 | while True: 109 | try: 110 | wc_dat, wc_src = await asyncio.wait_for(csa['proxy'].recvfrom(), 0.2) 111 | except asyncio.TimeoutError: 112 | continue 113 | try: 114 | logger.debug(f'proxy_tx: {wc_dat}, src {wc_src}') 115 | if len(wc_src[0]) != 9: 116 | logger.warning(f'proxy_tx: wc_src err: {wc_src}') 117 | continue 118 | dst_mac = int(wc_dat['dst'][0].split(':')[2], 16) 119 | if wc_src[0][1:3] != '00': 120 | frame = cdnet_l1.to_frame((f'{wc_src[0][1:3]}:{csa["net"]:02x}:{csa["mac"]:02x}', wc_src[1]), \ 121 | wc_dat['dst'], wc_dat['dat'], csa['mac'], dst_mac) 122 | logger.log(logging.VERBOSE, f'proxy_tx frame l1: {frame}') 123 | else: 124 | frame = cdnet_l0.to_frame((f'{wc_src[0][1:3]}:{csa["net"]:02x}:{csa["mac"]:02x}', wc_src[1]), \ 125 | wc_dat['dst'], wc_dat['dat']) 126 | logger.log(logging.VERBOSE, f'proxy_tx frame l0: {frame}') 127 | if csa['dev']: 128 | csa['dev'].send(frame) 129 | except Exception as err: 130 | logger.warning(f'proxy_tx: fmt err: {err}') 131 | 132 | 133 | async def dev_service(): # cdbus tty setup 134 | sock = CDWebSocket(ws_ns, 'dev') 135 | while True: 136 | dat, src = await sock.recvfrom() 137 | logger.debug(f'dev ser: {dat}') 138 | 139 | if dat['action'] == 'get': 140 | ports = get_ports() 141 | if csa['dev']: 142 | await sock.sendto({'ports': ports, 'port': csa['dev'].portstr, 'online': csa['dev'].online, 'net': csa['net'], 'mac': csa['mac']}, src) 143 | else: 144 | await sock.sendto({'ports': ports, 'port': None, 'online': False, 'net': csa['net'], 'mac': csa['mac']}, src) 145 | 146 | elif dat['action'] == 'open' and not csa['dev']: 147 | try: 148 | csa['dev'] = CDBusSerial(dat['port'], baud=dat['baud']) 149 | await sock.sendto('successed', src) 150 | except Exception as err: 151 | logger.warning(f'open dev err: {err}') 152 | await sock.sendto(f'err: {err}', src) 153 | 154 | elif dat['action'] == 'close' and csa['dev']: 155 | logger.info('stop dev') 156 | csa['dev'].stop() 157 | logger.info('stop finished') 158 | csa['dev'] = None 159 | await sock.sendto('successed', src) 160 | 161 | else: 162 | await sock.sendto('err: dev: unknown cmd', src) 163 | 164 | 165 | async def cfgs_service(): # read configs 166 | for cfg in os.listdir('configs'): 167 | if cfg.endswith('.json'): 168 | csa['cfgs'].append(cfg) 169 | 170 | sock = CDWebSocket(ws_ns, 'cfgs') 171 | while True: 172 | dat, src = await sock.recvfrom() 173 | logger.debug(f'cfgs ser: {dat}') 174 | 175 | if dat['action'] == 'get_cfgs': 176 | await sock.sendto(csa['cfgs'], src) 177 | 178 | elif dat['action'] == 'get_cfg': 179 | with open(os.path.join('configs', dat['cfg'])) as c_file: 180 | c = json5.load(c_file) 181 | await sock.sendto(c, src) 182 | 183 | else: 184 | await sock.sendto('err: cfgs: unknown cmd', src) 185 | 186 | 187 | async def port_service(): # alloc ports 188 | sock = CDWebSocket(ws_ns, 'port') 189 | while True: 190 | dat, src = await sock.recvfrom() 191 | path = src[0] 192 | logger.debug(f'port ser: {dat}, path: {path}') 193 | 194 | if path not in csa['palloc']: 195 | csa['palloc'][path] = [] 196 | 197 | if dat['action'] == 'clr_all': 198 | logger.debug(f'port clr_all') 199 | csa['palloc'][path] = [] 200 | await sock.sendto('successed', src) 201 | 202 | elif dat['action'] == 'get_port': 203 | if dat['port']: 204 | if dat['port'] not in csa['palloc'][path]: 205 | csa['palloc'][path].append(dat['port']) 206 | logger.debug(f'port alloc {dat["port"]}') 207 | await sock.sendto(dat['port'], src) 208 | else: 209 | logger.error(f'port alloc error') 210 | await sock.sendto(-1, src) 211 | else: 212 | p = -1 213 | for i in range(0x40, 0x80): 214 | if i not in csa['palloc'][path]: 215 | p = i 216 | csa['palloc'][path].append(p) 217 | break 218 | logger.debug(f'port alloc: {p}') 219 | await sock.sendto(p, src) 220 | 221 | else: 222 | await sock.sendto('err: port: unknown cmd', src) 223 | 224 | 225 | async def open_brower(): 226 | proc = await asyncio.create_subprocess_shell('/opt/google/chrome/chrome --app=http://localhost:8910') 227 | await proc.communicate() 228 | #proc = await asyncio.create_subprocess_shell('chromium --app=http://localhost:8910') 229 | #await proc.communicate() 230 | logger.info('open brower done.') 231 | 232 | 233 | if __name__ == "__main__": 234 | csa['async_loop'] = asyncio.new_event_loop() 235 | asyncio.set_event_loop(csa['async_loop']) 236 | csa['proxy'] = CDWebSocket(ws_ns, 'proxy') 237 | csa['async_loop'].create_task(start_web()) 238 | csa['async_loop'].create_task(cfgs_service()) 239 | csa['async_loop'].create_task(dev_service()) 240 | csa['async_loop'].create_task(port_service()) 241 | csa['async_loop'].create_task(cdbus_proxy_service()) 242 | 243 | from plugins.iap import iap_init 244 | iap_init(csa) 245 | 246 | #csa['async_loop'].create_task(open_brower()) 247 | logger.info('Please open url: http://localhost:8910') 248 | csa['async_loop'].run_forever() 249 | 250 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | CDBUS GUI Tool 2 | ======================================= 3 | 4 | CDBUS GUI is an open-source, cross-platform serial debugging tool supporting register read/write, waveform plotting, log printing, IAP upgrades, and more. 5 | 6 | #### Download: 7 | `git clone --recursive https://github.com/dukelec/cdbus_gui` 8 | 9 | 10 | #### Dependence: 11 | Python version >= 3.8 12 | `pip3 install pythoncrc json5 websockets pyserial u-msgpack-python aiohttp IntelHex` 13 | 14 | #### Usage: 15 | Run `main.py`, then open the following URL in your web browser: http://localhost:8910 16 | 17 | The underlying protocol for serial port is CDBUS, which uses the following frame format: 18 | `src, dst, len, [payload], crc_l, crc_h` 19 | 20 | Each frame includes a 3-byte header, a variable-length payload, and a 2-byte CRC (identical to Modbus CRC). 21 | For more information on the CDBUS protocol, please refer to: 22 | - https://cdbus.org 23 | 24 | The payload is encoded using the CDNET protocol. For detailed information, please refer to: 25 | - https://github.com/dukelec/cdnet 26 | - https://github.com/dukelec/cdnet/wiki/CDNET-Intro-and-Demo 27 | 28 | 29 | ### Index Page 30 | - "Serial": The first field accepts a port name or matching string (e.g., `ACM0`, `Bridge`, `2E3C:5740`), with wildcard support. 31 | The second field is the baud rate, supporting arbitrary values. 32 | - "Available": Lists all serial ports on the computer. 33 | - "Devices": Quickly open the debug page for each device (list expands automatically). 34 | - "Logs": Aggregates logs from all devices. 35 | (Pressing `Enter` inserts a blank line; Holding `Alt` or `Ctrl + Alt` allows block selection.) 36 | - Auto-reconnect supported for serial ports. 37 | - Modified settings are saved automatically. 38 | - Supports ANSI color codes. 39 | 40 | 41 | 42 | 43 | ### Device Page 44 | - Displays registers for reading and writing. 45 | - Hover over a register to view its description, type, and default value. 46 | - Registers are grouped for atomic R/W; groups can be edited freely. 47 | - `R` reads a group, `W` writes it. `Read All` / `Write All` act on all groups. 48 | - Supports arrays and multiple formats, displayed in hexadecimal (`H`) or as byte arrays (`B`). 49 | - Small notches on the `R`/`W` buttons indicate gaps; groups are read before writing so that the data in gaps remains unchanged. 50 | 51 | 52 | 53 | 54 | 55 | #### Waveform Plots: 56 | - Supports real-time display and long data recording, including FFT visualization. 57 | - Supports multiple windows with adjustable sizes. 58 | - Mouse wheel with `Shift` or `Ctrl` scales the `X` or `Y` axis; by default, both axes scale together. 59 | - Touchscreen zoom and independent `X`/`Y` scaling are supported. 60 | - Double-click to reset the view (zoom to fit). Hold the left or middle mouse button to pan. 61 | - Individual channels can be toggled on or off for clarity. 62 | - Multiple plots can be started or stopped simultaneously via the `dbg_raw_msk` register. 63 | - Supports formula-based waveforms (e.g., `u_cal` below). Click `Re-Calc` to refresh the plot after modifying or adding formulas. 64 | 65 | 66 | 67 | 68 | 69 | 70 | #### Picture Preview: 71 | - Preview images (e.g., JPEG files) sent from the device. 72 | 73 | 74 | 75 | 76 | #### IAP and Data Import/Export: 77 | - IAP supports readback verification, device-side CRC validation, or no validation. 78 | - IAP supports Intel HEX files with multiple segments. 79 | - Register data, log outputs, and waveform data can be exported and imported together (in MessagePack format). 80 | 81 | 82 | ### JSON Format 83 | - The register list can be auto-generated by the MCU (printed when it powers up). 84 | - Uses JSON5 format, supporting hexadecimal values and comments. 85 | - The "fmt" string with "[]" is an array, which displays all data in one text box. 86 | - "{}" denotes struct arrays; each array element has its own text box containing multiple members of the struct. 87 | 88 | **E.g.** `cdstep-v6.json` 89 | ```json5 90 | { 91 | "reg": { 92 | // fmt: [c]: string, b: int8_t, B: uint8_t, h: int16_t, H: uint16_t, i: int32_t, I: uint32_t 93 | // q: int64_t, Q: uint64_t, f: float, d: double 94 | // show: 0: normal, 1: hex, 2: bytes 95 | "list": [ 96 | [ 0x0000, 2, "H", 1, "magic_code", "Magic code: 0xcdcd" ], 97 | [ 0x0002, 2, "H", 1, "conf_ver", "Config version" ], 98 | [ 0x0004, 1, "B", 0, "conf_from", "0: default config, 1: all from flash, 2: partly from flash" ], 99 | [ 0x0005, 1, "B", 0, "do_reboot", "1: reboot to bl, 2: reboot to app" ], 100 | [ 0x0007, 1, "b", 0, "save_conf", "Write 1 to save current config to flash" ], 101 | 102 | [ 0x000c, 1, "B", 1, "bus_cfg_mac", "RS-485 port id, range: 0~254" ], 103 | [ 0x0010, 4, "I", 0, "bus_cfg_baud_l", "RS-485 baud rate for first byte" ], 104 | [ 0x0014, 4, "I", 0, "bus_cfg_baud_h", "RS-485 baud rate for follow bytes" ], 105 | // ... 106 | 107 | [ 0x00bc, 4, "i", 0, "tc_pos", "Set target position" ], 108 | [ 0x00c0, 4, "I", 0, "tc_speed", "Set target speed" ], 109 | [ 0x00c4, 4, "I", 0, "tc_accel", "Set target accel" ], 110 | [ 0x00c8, 4, "I", 0, "tc_accel_emg", "Set emergency accel" ], 111 | 112 | [ 0x00d4, 4, "f", 0, "pid_pos_kp", "" ], 113 | [ 0x00d8, 4, "f", 0, "pid_pos_ki", "" ], 114 | [ 0x00dc, 4, "f", 0, "pid_pos_kd", "" ], 115 | [ 0x0100, 4, "i", 0, "cal_pos", "PID input position" ], 116 | [ 0x0104, 4, "f", 0, "cal_speed", "PID output speed" ], 117 | 118 | [ 0x0108, 1, "B", 0, "state", "0: disable drive, 1: enable drive" ], 119 | 120 | // --------------- Follows are not writable: ------------------- 121 | [ 0x0109, 1, "B", 0, "tc_state", "t_curve: 0: stop, 1: run" ], 122 | [ 0x010c, 4, "i", 0, "cur_pos", "Motor current position" ], 123 | [ 0x0110, 4, "f", 0, "tc_vc", "Motor current speed" ], 124 | [ 0x0114, 4, "f", 0, "tc_ac", "Motor current accel" ], 125 | 126 | [ 0x0124, 4, "I", 0, "loop_cnt", "Count for plot" ], 127 | [ 0x0128, 10, "[c]", 0, "string_test", "String test" ] 128 | ], 129 | 130 | // button groups 131 | "reg_r": [["magic_code","save_conf"],["bus_cfg_mac","bus_cfg_tx_pre_len"],["dbg_en"],["qxchg_mcast"], 132 | ["qxchg_set","qxchg_ret"],["dbg_raw_msk"],["dbg_raw[0]","dbg_raw[1]"],["ref_volt","lim_en"], 133 | ["tc_pos","tc_accel"],["tc_accel_emg"],["pid_pos_kp","pid_pos_kd"],["cal_pos","cal_speed"], 134 | ["state"],["tc_state","cur_pos"],["tc_vc","tc_ac"],["loop_cnt"],["string_test"]], 135 | "reg_w": [["magic_code","conf_ver"],["do_reboot"],["save_conf"],["bus_cfg_mac"],["bus_cfg_baud_l","bus_cfg_baud_h"], 136 | ["bus_cfg_filter_m"],["bus_cfg_mode"],["bus_cfg_tx_permit_len","bus_cfg_tx_pre_len"],["dbg_en"], 137 | ["qxchg_mcast"],["qxchg_set","qxchg_ret"],["dbg_raw_msk"],["dbg_raw[0]","dbg_raw[1]"], 138 | ["ref_volt","md_val"],["set_home"],["lim_en"],["tc_pos"],["tc_speed","tc_accel"],["tc_accel_emg"], 139 | ["pid_pos_kp","pid_pos_kd"],["state"],["string_test"]], 140 | "less_r": [["tc_pos","tc_accel"],["state","loop_cnt"]], 141 | "less_w": [["tc_pos"],["tc_speed","tc_accel"],["state"]] 142 | }, 143 | 144 | "plot": { 145 | "mask": "dbg_raw_msk", 146 | "plots": [ 147 | { 148 | // "color": ["black", "red", "green", ...] 149 | "fmt": "H1.iBiiff", 150 | "label": ["N", "tc_pos", "tc_state", "cal_pos", "cur_pos", "tc_vc", "tc_va"], 151 | "cal": { 152 | "pos_err": "_d[4].at(-1) - _d[3].at(-1)" // data4 - data3 153 | }, 154 | "fft": { 155 | "sample_rate": 5000, 156 | "size": 4096 157 | } 158 | }, { 159 | "fmt": "H1.ifif", 160 | "label": ["N", "pid target", "i_term", "last_in", "cal_speed"] 161 | } 162 | ] 163 | }, 164 | 165 | "iap": { "reboot": 0x0005, "keep_bl": 0x0006 } 166 | } 167 | ``` 168 | 169 | - "reg_r" and "reg_w" are the default register group configurations; you can leave them empty and edit via the UI (after editing, the browser debug window will print them; "less_r" and "less_w" work the same way). 170 | - The "fmt" of "plot" data corresponds to two packet formats: 171 | * "x1 a1 b1 a2 b2 …" – x-axis data is shared across groups. "H" denotes a uint16_t counter, incremented each loop; a number after "H" gives the delta to recover subsequent x values. 172 | * "x1 a1 b1 x2 a2 b2 …" – each data set has its own x value, suitable for variable loop periods. 173 | 174 | 175 | ### More Info 176 | As a side note, `CDNET Address` refers to the IPv6 concept, allowing strings to represent different addresses, defined as follows. 177 | 178 | ``` 179 | /* CDNET Address string formats: 180 | * 181 | * localhost local link unique local multicast 182 | * 10:00:00 183 | * level0: 00:NN:MM 184 | * level1: 80:NN:MM a0:NN:MM f0:MH:ML 185 | * 186 | * Notes: 187 | * NN: net_id, MM: mac_addr, MH/ML: multicast_id (H: high byte, L: low byte) 188 | */ 189 | ``` 190 | 191 | - Broadcast and multicast can also use the "link-local" format; the "multicast" format is generally not needed for simple cases. 192 | - "Unique local" is used only across network segments — for example, when multiple subnets each contain multiple devices. 193 | 194 | Serial CDNET packets can be directly mapped to UDP packets, enabling multiple software applications to access the same serial device. Please refer to: 195 | https://github.com/dukelec/cdnet_tun 196 | 197 | To access a serial device via UDP, run the `main_udp.py` program instead. 198 | -------------------------------------------------------------------------------- /main_udp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Software License Agreement (MIT License) 4 | # 5 | # Author: Duke Fong 6 | 7 | """CDBUS GUI Tool 8 | 9 | Args: 10 | --help | -h # this help message 11 | --verbose | -v # debug level: verbose 12 | --debug | -d # debug level: debug 13 | --local-net LOCAL_NET # default: 0 14 | --local-mac LOCAL_MAC # default: 0 15 | --ip6-prefix PREFIX # default: fdcd:: 16 | --port-base BASE # default: 0xcd00 17 | """ 18 | 19 | import os, sys, re 20 | import _thread 21 | import socket, select, ipaddress 22 | import time, datetime 23 | import copy, json5 24 | import asyncio, aiohttp 25 | import websockets 26 | from cd_ws import CDWebSocket, CDWebSocketNS 27 | from web_serve import ws_ns, start_web 28 | 29 | sys.path.append(os.path.join(os.path.dirname(__file__), 'pycdnet')) 30 | 31 | from cdnet.utils.log import * 32 | from cdnet.utils.cd_args import CdArgs 33 | 34 | 35 | csa = { 36 | 'async_loop': None, 37 | 'udp': False, 38 | 'udp_socks': {}, # port: [sock_00, sock_80, ...] 39 | 'net': 0x00, # local net 40 | 'mac': 0x00, # local mac 41 | 'proxy': None, # cdbus frame proxy socket 42 | 'cfgs': [], # config list 43 | 'palloc': {}, # ports alloc, url_path: [] 44 | } 45 | 46 | args = CdArgs() 47 | if args.get("--help", "-h") != None: 48 | print(__doc__) 49 | exit() 50 | 51 | csa['net'] = int(args.get("--local-net", dft="0x00"), 0) 52 | csa['mac'] = int(args.get("--local-mac", dft="0x00"), 0) 53 | udp_ip_prefix = args.get("--ip6-prefix", dft="fdcd::") 54 | udp_port_base = int(args.get("--port-base", dft="0xcd00"), 0) 55 | 56 | cdnet_local_addr = [ 57 | f"00:{csa['net']:02x}:{csa['mac']:02x}", 58 | f"80:{csa['net']:02x}:{csa['mac']:02x}" 59 | ] 60 | 61 | if args.get("--verbose", "-v") != None: 62 | logger_init(logging.VERBOSE) 63 | elif args.get("--debug", "-d") != None: 64 | logger_init(logging.DEBUG) 65 | else: 66 | logger_init(logging.INFO) 67 | 68 | logging.getLogger('websockets').setLevel(logging.WARNING) 69 | logger = logging.getLogger(f'cdgui') 70 | 71 | 72 | def addr_ip2cdnet(addr): 73 | full_ip = ipaddress.IPv6Address(addr).exploded 74 | tmp = full_ip[32:] 75 | return tmp[0:5] + ':' + tmp[5:] 76 | 77 | def addr_cdnet2ip(addr): 78 | tmp = addr.split(':') 79 | return f'{udp_ip_prefix}{tmp[0]}:{tmp[1]}{tmp[2]}' 80 | 81 | 82 | # proxy to html: ('/x0:00:dev_mac', host_port) <- ('server', 'proxy'): { 'src': src, 'dat': payloads } 83 | async def proxy_rx_rpt(rx): 84 | src, dst_port, dat = rx 85 | logger.debug(f'rx_rpt: src: {src}, dst_port: {dst_port}, dat: {dat}') 86 | if dst_port == 0x9 or src[1] == 0x1: 87 | time_str = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3].encode() 88 | # dbg and dev_info msg also send to index.html 89 | dat4idx = re.sub(b'\n(?!$)', b'\n' + b' ' * 25, dat) # except the end '\n' 90 | dat4idx = time_str + b' [' + src[0].encode() + b']' + b': ' + dat4idx 91 | if src[1] == 0x1: 92 | dat4idx += b'\n' 93 | await csa['proxy'].sendto({'src': src, 'dat': dat4idx}, (f'/', 0x9)) 94 | dat = re.sub(b'\n(?!$)', b'\n' + b' ' * 14, dat) 95 | dat = time_str + b': ' + dat 96 | ret = await csa['proxy'].sendto({'src': src, 'dat': dat}, (f'/{src[0]}', dst_port)) 97 | if ret: 98 | logger.warning(f'rx_rpt err: {ret}: /{src[0]}:{dst_port}, {dat}') 99 | 100 | 101 | proxy_rx_pause = False 102 | proxy_rx_paused = False 103 | 104 | async def udp_socks_update(remove=False): 105 | global proxy_rx_pause 106 | if remove: 107 | proxy_rx_pause = True 108 | while True: 109 | await asyncio.sleep(0.1) 110 | if proxy_rx_paused: 111 | break 112 | new_ports = [] 113 | for url in csa['palloc']: 114 | for p in csa['palloc'][url]: 115 | if p not in new_ports: 116 | new_ports.append(p) 117 | for p in list(csa['udp_socks'].keys()): 118 | if p not in new_ports: 119 | for s in csa['udp_socks'][p]: 120 | s.close() 121 | del(csa['udp_socks'][p]) 122 | for p in new_ports: 123 | if p not in csa['udp_socks']: 124 | csa['udp_socks'][p] = [] 125 | for caddr in cdnet_local_addr: 126 | ip_addr = addr_cdnet2ip(caddr) 127 | s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 128 | s.bind((ip_addr, p + udp_port_base)) 129 | csa['udp_socks'][p].append(s) 130 | if remove: 131 | proxy_rx_pause = False 132 | await asyncio.sleep(0.1) 133 | 134 | 135 | def proxy_rx(): 136 | global proxy_rx_paused 137 | logger.info('start proxy_rx') 138 | while True: 139 | try: 140 | if proxy_rx_pause: 141 | proxy_rx_paused = True 142 | time.sleep(0.1) 143 | continue 144 | proxy_rx_paused = False 145 | socks = [x for v in csa['udp_socks'].values() for x in v] 146 | readable, _, _ = select.select(socks, [], [], 0.2) 147 | if not readable: 148 | continue 149 | for s in readable: 150 | dat, src_addr = s.recvfrom(256) 151 | dst_port = s.getsockname()[1] - udp_port_base 152 | src_ip = addr_ip2cdnet(src_addr[0]) 153 | src_port = src_addr[1] 154 | rx = (src_ip, src_port), dst_port, dat 155 | asyncio.run_coroutine_threadsafe(proxy_rx_rpt(rx), csa['async_loop']).result() 156 | except Exception as err: 157 | logger.warning(f'proxy_rx: err: {err}') 158 | 159 | _thread.start_new_thread(proxy_rx, ()) 160 | 161 | # proxy to dev, ('/x0:00:dev_mac', host_port) -> ('server', 'proxy'): { 'dst': dst, 'dat': payloads } 162 | async def cdbus_proxy_service(): 163 | while True: 164 | try: 165 | wc_dat, wc_src = await asyncio.wait_for(csa['proxy'].recvfrom(), 0.2) 166 | except asyncio.TimeoutError: 167 | continue 168 | try: 169 | logger.debug(f'proxy_tx: {wc_dat}, src {wc_src}') 170 | if len(wc_src[0]) != 9: 171 | logger.warning(f'proxy_tx: wc_src err: {wc_src}') 172 | continue 173 | dst_ip = addr_cdnet2ip(wc_dat['dst'][0]) 174 | dst_port = wc_dat['dst'][1] 175 | src_port = wc_src[1] 176 | if wc_src[0][1:3] == '00': 177 | s = csa['udp_socks'][src_port][0] 178 | else: 179 | s = csa['udp_socks'][src_port][1] 180 | s.sendto(wc_dat['dat'], (dst_ip, dst_port)) 181 | except Exception as err: 182 | logger.warning(f'proxy_tx: err: {err}') 183 | 184 | 185 | async def dev_service(): # cdbus tty setup 186 | sock = CDWebSocket(ws_ns, 'dev') 187 | while True: 188 | dat, src = await sock.recvfrom() 189 | logger.debug(f'dev ser: {dat}') 190 | 191 | if dat['action'] == 'get': 192 | await sock.sendto('udp', src) 193 | else: 194 | await sock.sendto('err: dev: unknown cmd', src) 195 | 196 | 197 | async def cfgs_service(): # read configs 198 | for cfg in os.listdir('configs'): 199 | if cfg.endswith('.json'): 200 | csa['cfgs'].append(cfg) 201 | 202 | sock = CDWebSocket(ws_ns, 'cfgs') 203 | while True: 204 | dat, src = await sock.recvfrom() 205 | logger.debug(f'cfgs ser: {dat}') 206 | 207 | if dat['action'] == 'get_cfgs': 208 | await sock.sendto(csa['cfgs'], src) 209 | 210 | elif dat['action'] == 'get_cfg': 211 | with open(os.path.join('configs', dat['cfg'])) as c_file: 212 | c = json5.load(c_file) 213 | await sock.sendto(c, src) 214 | 215 | else: 216 | await sock.sendto('err: cfgs: unknown cmd', src) 217 | 218 | 219 | async def port_service(): # alloc ports 220 | sock = CDWebSocket(ws_ns, 'port') 221 | while True: 222 | dat, src = await sock.recvfrom() 223 | path = src[0] 224 | logger.debug(f'port ser: {dat}, path: {path}') 225 | 226 | if path not in csa['palloc']: 227 | csa['palloc'][path] = [] 228 | 229 | if dat['action'] == 'clr_all': 230 | logger.debug(f'port clr_all') 231 | csa['palloc'][path] = [] 232 | await udp_socks_update(True) 233 | await sock.sendto('successed', src) 234 | 235 | elif dat['action'] == 'get_port': 236 | if dat['port']: 237 | if dat['port'] not in csa['palloc'][path]: 238 | csa['palloc'][path].append(dat['port']) 239 | logger.debug(f'port alloc {dat["port"]}') 240 | await udp_socks_update() 241 | await sock.sendto(dat['port'], src) 242 | else: 243 | logger.error(f'port alloc error') 244 | await sock.sendto(-1, src) 245 | else: 246 | p = -1 247 | for i in range(0x40, 0x80): 248 | if i not in csa['palloc'][path]: 249 | p = i 250 | csa['palloc'][path].append(p) 251 | break 252 | logger.debug(f'port alloc: {p}') 253 | await udp_socks_update() 254 | await sock.sendto(p, src) 255 | 256 | else: 257 | await sock.sendto('err: port: unknown cmd', src) 258 | 259 | 260 | async def open_brower(): 261 | proc = await asyncio.create_subprocess_shell('/opt/google/chrome/chrome --app=http://localhost:8910') 262 | await proc.communicate() 263 | #proc = await asyncio.create_subprocess_shell('chromium --app=http://localhost:8910') 264 | #await proc.communicate() 265 | logger.info('open brower done.') 266 | 267 | 268 | if __name__ == "__main__": 269 | csa['async_loop'] = asyncio.new_event_loop() 270 | asyncio.set_event_loop(csa['async_loop']) 271 | csa['proxy'] = CDWebSocket(ws_ns, 'proxy') 272 | csa['async_loop'].create_task(start_web()) 273 | csa['async_loop'].create_task(cfgs_service()) 274 | csa['async_loop'].create_task(dev_service()) 275 | csa['async_loop'].create_task(port_service()) 276 | csa['async_loop'].create_task(cdbus_proxy_service()) 277 | 278 | from plugins.iap import iap_init 279 | iap_init(csa) 280 | 281 | #csa['async_loop'].create_task(open_brower()) 282 | logger.info('Please open url: http://localhost:8910') 283 | csa['async_loop'].run_forever() 284 | 285 | -------------------------------------------------------------------------------- /tools/cdg_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Software License Agreement (MIT License) 4 | # 5 | # Author: Duke Fong 6 | 7 | import re, struct, math 8 | 9 | 10 | def readable_float(num, double=False): 11 | if not math.isfinite(num): 12 | return str(num) 13 | fixed = 18 if double else 9 14 | precision = 16 if double else 7 15 | n = f"{num:.{precision}g}" 16 | if 'e' in n: 17 | return n 18 | parts = n.split('.') 19 | int_part = parts[0] 20 | dec_part = parts[1] if len(parts) > 1 else '0' 21 | 22 | dec_part = dec_part.ljust(fixed, '0') 23 | n = f"{int_part}.{dec_part}" 24 | 25 | for _ in range(fixed // 3): 26 | if n.endswith('000'): 27 | n = n[:-3] 28 | else: 29 | break 30 | if n.endswith('.'): 31 | n += '0' 32 | return n 33 | 34 | 35 | def dat2hex(dat, join='', le=False): 36 | dat = reversed(dat) if le else dat 37 | return join.join([f'{x:02x}' for x in dat]) 38 | 39 | def hex2dat(hex_, le=False): 40 | hex_ = hex_.replace('0x', '').replace(' ','') 41 | ret = bytes.fromhex(hex_) 42 | return bytes(reversed(ret)) if le else ret 43 | 44 | def hex2float(hex_): 45 | parts = hex_.split('.') 46 | if len(parts) > 1: 47 | sign = -1 if hex_.startswith('-') else 1 48 | val = int(parts[0], 16) + int(parts[1], 16) / pow(16, len(parts[1])) * sign 49 | else: 50 | val = int(parts[0], 16) 51 | return val 52 | 53 | def val2hex(val, fixed=4, prefix=False, upper=False, float_=False): 54 | sign = '-' if val < 0 else '' 55 | val = abs(val) 56 | int_ = int(val) 57 | if isinstance(val, float): 58 | float_ = True 59 | sub_ = val - int_ if float_ else 0.0 60 | int_str = f'{int(int_):x}' 61 | int_str = (fixed - len(int_str)) * '0' + int_str 62 | sub_str = '' 63 | for i in range(16): 64 | y = int(sub_ * 16) 65 | sub_str += f'{y:x}' 66 | sub_ = sub_ * 16 - y 67 | if float_: 68 | str_ = f'{int_str}.{sub_str}' 69 | str_ = str_.rstrip('0') 70 | if str_.endswith('.'): 71 | str_ += '0' 72 | else: 73 | str_ = f'{int_str}' 74 | if upper: 75 | str_ = str_.upper() 76 | if prefix: 77 | str_ = '0x' + str_ 78 | return sign + str_ 79 | 80 | 81 | def fmt_size(fmt): 82 | f = re.sub(r'[\W_]', '', fmt) # remove non-word chars 83 | len_ = 0; 84 | i = 0 85 | while i < len(f): 86 | fnext = f[i+1] if i < len(f) - 1 else '' 87 | if fnext.isdigit(): # e.g. '{H,B2}' 88 | len_ += int(fnext) 89 | i += 2 90 | continue 91 | if f[i] == 'c' or f[i] == 'b' or f[i] == 'B': 92 | len_ += 1 93 | elif f[i] == 'h' or f[i] == 'H': 94 | len_ += 2 95 | elif f[i] == 'i' or f[i] == 'I' or f[i] == 'f': 96 | len_ += 4 97 | elif f[i] == 'q' or f[i] == 'Q' or f[i] == 'd': 98 | len_ += 8 99 | i += 1 100 | return len_; 101 | 102 | 103 | def reg2str(dat, ofs, fmt, show): 104 | ret = '' 105 | f = re.sub(r'[\W_]', '', fmt) # remove non-word chars 106 | i = 0 107 | while i < len(f): 108 | fnext = f[i+1] if i < len(f) - 1 else '' 109 | if f[i] == 'c': 110 | c_len = 1 111 | if show == 1: 112 | ret = ' '.join(filter(None, [ret, f"{val2hex(struct.unpack(' 6 | 7 | """CMD Tool for CDBUS GUI 8 | 9 | Args: 10 | --help | -h # this help message 11 | --verbose | -v # debug level: verbose 12 | --debug | -d # debug level: debug 13 | 14 | --cfg CFG_FILE # always required 15 | 16 | --tty PORT # default: ttyACM0 17 | --baud BAUD # default: 115200 18 | --local LOCAL_MAC # default: 0 19 | --dev TARGET # default: 00:00:fe 20 | 21 | --quiet | -q 22 | --pretend | -p 23 | 24 | --reg REG_NAME # read reg if not specify val 25 | --val REG_VAL 26 | 27 | --export MPK_FILE # only print when file path empty 28 | --import MPK_FILE 29 | """ 30 | 31 | import os, sys, _thread 32 | import json5 33 | import pprint 34 | import umsgpack 35 | from cdg_helper import * 36 | 37 | sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'pycdnet')) 38 | 39 | from cdnet.utils.log import * 40 | from cdnet.utils.cd_args import CdArgs 41 | from cdnet.dev.cdbus_serial import CDBusSerial 42 | from cdnet.dispatch import * 43 | 44 | 45 | csa = { 46 | 'args': None, 47 | 48 | 'dev_addr': None, 49 | 'quiet': False, 50 | 'pretend': False, 51 | 52 | 'logger': None, 53 | 'sock': None, 54 | 55 | 'cfg': None, 56 | 57 | # reg_name(.n) : { 58 | # fmt: 59 | # show: 60 | # addr: 61 | # len: 62 | # desc: '...' 63 | # } 64 | 'regs': {} 65 | } 66 | 67 | 68 | def cdg_cmd_init(doc=None): 69 | global reg_name, reg_val, export_file, import_file, sock_dbg 70 | 71 | args = CdArgs() 72 | csa['args'] = args 73 | tty_str = args.get("--tty", dft="ttyACM0") 74 | baud = int(args.get("--baud", dft="115200"), 0) 75 | local_mac = int(args.get("--local", dft="0x00"), 0) 76 | csa['dev_addr'] = args.get("--dev", dft="00:00:fe") 77 | cfg_file = args.get("--cfg") 78 | 79 | csa['quiet'] = args.get("--quiet", "-q") != None 80 | csa['pretend'] = args.get("--pretend", "-p") != None 81 | 82 | reg_name = args.get("--reg") 83 | reg_val = args.get("--val") # string value 84 | export_file = args.get("--export") # mpk file 85 | import_file = args.get("--import") 86 | 87 | if args.get("--help", "-h") != None or cfg_file == None: 88 | print(f'{doc}\n{__doc__}' if doc else __doc__) 89 | exit(-1 if cfg_file == None else 0) 90 | 91 | if args.get("--verbose", "-v") != None: 92 | logger_init(logging.VERBOSE) 93 | elif args.get("--debug", "-d") != None: 94 | logger_init(logging.DEBUG) 95 | else: 96 | logger_init(logging.INFO) 97 | 98 | csa['logger'] = logging.getLogger(f'cdgui_cmd') 99 | 100 | dev = CDBusSerial(tty_str, baud=baud) 101 | 102 | CDNetIntf(dev, mac=local_mac) 103 | csa['sock'] = CDNetSocket(('', 0x40)) 104 | sock_dbg = CDNetSocket(('', 9)) 105 | 106 | with open(cfg_file) as f: 107 | csa['cfg'] = json5.load(f) 108 | _thread.start_new_thread(dbg_echo, ()) 109 | list_all_reg() 110 | 111 | if not csa['quiet']: 112 | print('Device Info:', cd_read_info(csa['dev_addr'])) 113 | 114 | 115 | def dbg_echo(): 116 | while True: 117 | rx = sock_dbg.recvfrom() 118 | if not csa['quiet']: 119 | csa['logger'].info(f'#{rx[1][0][-2:]} \x1b[0;37m' + rx[0][:-1].decode() + '\x1b[0m') 120 | 121 | 122 | # for unicast only 123 | def cd_reg_rw(dev_addr, reg_addr, write=None, read=0, timeout=0.8, retry=3): 124 | if write != None: 125 | tx_dat = b'\x20'+struct.pack("= addr_len_list[i][0] and \ 215 | csa['regs'][name]['addr'] < addr_len_list[i][0] + addr_len_list[i][1]: 216 | return addr_len_list[i] 217 | return None 218 | 219 | 220 | def read_reg(name): 221 | reg = csa['regs'][name] 222 | grp = get_rw_grp(name, 'r') 223 | if not grp or not reg: 224 | if not csa['quiet']: 225 | print(f'reg: {name} read disabled') 226 | exit(-1) 227 | dat = cd_reg_rw(csa['dev_addr'], grp[0], read=grp[1]) 228 | if reg['fmt'].startswith('['): 229 | ret = [] 230 | join = '' if reg['fmt'][1] == 'c' and reg['show'] == 0 else ' ' 231 | fmt_len = fmt_size(reg['fmt']) 232 | i = 0 233 | while i < round(reg['len']/fmt_len): 234 | cur_ofs = reg['addr'] - grp[0] + fmt_len * i 235 | r_, ofs = reg2str(dat[1:], reg['addr'] - grp[0] + fmt_len * i, reg['fmt'], reg['show']) 236 | ret.append(r_) 237 | i += round((ofs - cur_ofs) / fmt_len); 238 | return join.join(ret) 239 | return reg2str(dat[1:], reg['addr'] - grp[0], reg['fmt'], reg['show'])[0] 240 | 241 | 242 | def write_reg(name, str_): 243 | reg = csa['regs'][name] 244 | grp = get_rw_grp(name, 'w') 245 | if not grp or not reg: 246 | if not csa['quiet']: 247 | print(f'reg: {name} write disabled') 248 | exit(-1) 249 | dat = cd_reg_rw(csa['dev_addr'], grp[0], read=grp[1]) 250 | dat = dat[1:] 251 | if reg['fmt'].startswith('['): 252 | ret = [] 253 | fmt_len = fmt_size(reg['fmt']) 254 | for i in range(round(reg['len']/fmt_len)): 255 | dat = str2reg(dat, reg['addr'] - grp[0] + fmt_len * i, reg['fmt'], reg['show'], str_, i) 256 | else: 257 | dat = str2reg(dat, reg['addr'] - grp[0], reg['fmt'], reg['show'], str_, 0) 258 | cd_reg_rw(csa['dev_addr'], grp[0], write=dat) 259 | 260 | 261 | 262 | if __name__ == "__main__": 263 | cdg_cmd_init() 264 | 265 | if reg_name != None: 266 | if reg_val == None: 267 | print(read_reg(reg_name)) 268 | elif not csa['pretend']: 269 | write_reg(reg_name, reg_val) 270 | 271 | elif export_file != None: 272 | reg_str = {} 273 | for name in csa['regs']: 274 | if get_rw_grp(name, 'r') != None: 275 | reg_str[name] = read_reg(name) 276 | if not csa['quiet']: 277 | pprint.pp(reg_str) 278 | out_data = umsgpack.packb({'version': 'cdgui v1', 'reg': reg_str}) 279 | if export_file: 280 | with open(export_file, 'wb') as f: 281 | f.write(out_data) 282 | 283 | elif import_file != None: 284 | with open(import_file, 'rb') as f: 285 | in_file = f.read() 286 | in_data = umsgpack.unpackb(in_file) 287 | for name in in_data['reg']: 288 | val = in_data['reg'][name] 289 | if not csa['quiet']: 290 | print(f' {name}: {val}') 291 | if get_rw_grp(name, 'w') != None: 292 | if get_rw_grp(name, 'r') != None: 293 | ori = read_reg(name) 294 | if ori != val: 295 | if not csa['quiet']: 296 | print(f' + write: {ori} -> {val}') 297 | if not csa['pretend']: 298 | write_reg(name, val) 299 | else: 300 | if not csa['quiet']: 301 | print(f' + write only: {val}') 302 | if not csa['pretend']: 303 | write_reg(name, val) 304 | else: 305 | if get_rw_grp(name, 'r') != None: 306 | ori = read_reg(name) 307 | if ori != val and not csa['quiet']: 308 | print(f' - write disabled: current: {ori}') 309 | elif not csa['quiet']: 310 | print(f' - write & read disabled') 311 | 312 | -------------------------------------------------------------------------------- /html/utils/helper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | function sleep(ms) { 8 | return new Promise(resolve => setTimeout(resolve, ms)); 9 | } 10 | 11 | async function read_file(file) { 12 | return await new Promise((resolve, reject) => { 13 | let reader = new FileReader(); 14 | 15 | reader.onload = () => { 16 | resolve(new Uint8Array(reader.result)); 17 | }; 18 | reader.onerror = reject; 19 | reader.readAsArrayBuffer(file); 20 | }) 21 | } 22 | 23 | async function load_img(img, url) { 24 | let ret = -1; 25 | await new Promise(resolve => { 26 | img.src = url; 27 | img.onload = () => { ret = 0; resolve(); }; 28 | img.onerror = () => { console.error(`load_img: ${url}`); resolve(); }; 29 | }); 30 | return ret; 31 | } 32 | 33 | function date2num() { 34 | let d = (new Date()).toLocaleString('en-GB'); 35 | let s = d.split(/[^0-9]/); 36 | return `${s[2]}${s[1]}${s[0]}${s[4]}${s[5]}${s[6]}`; 37 | } 38 | 39 | function timestamp() { 40 | let date = new Date(); 41 | let time = date.toLocaleString('en-GB'); 42 | return time.split(' ')[1] + '.' + String(date.getMilliseconds()).padStart(3, '0'); 43 | } 44 | 45 | async function sha256(dat) { 46 | const hashBuffer = await crypto.subtle.digest('SHA-256', dat); 47 | return new Uint8Array(hashBuffer); 48 | } 49 | 50 | async function aes256(dat, key, type='encrypt') { 51 | let iv = new Uint8Array(16); // zeros 52 | let _key = await crypto.subtle.importKey('raw', key, {name: 'AES-CBC'}, false, ['encrypt', 'decrypt']); 53 | 54 | if (type == 'encrypt') 55 | return new Uint8Array(await crypto.subtle.encrypt({name: 'AES-CBC', iv: iv}, _key, dat)); 56 | else 57 | return new Uint8Array(await crypto.subtle.decrypt({name: 'AES-CBC', iv: iv}, _key, dat)); 58 | } 59 | 60 | function dat2hex(dat, join='', le=false) { 61 | let dat_array = Array.from(dat); 62 | if (le) 63 | dat_array = dat_array.reverse(); 64 | return dat_array.map(b => b.toString(16).padStart(2, '0')).join(join); 65 | } 66 | 67 | function hex2dat(hex, le=false) { 68 | hex = hex.replace('0x', '').replace(/\s/g,'') 69 | let ret = new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))); 70 | if (le) 71 | return ret.reverse(); 72 | return ret; 73 | } 74 | 75 | function hex2float(hex) { 76 | let parts = hex.split('.'); 77 | let val; 78 | if (parts.length > 1) { 79 | let sign = hex.startsWith('-') ? -1 : 1; 80 | val = parseInt(parts[0], 16) + parseInt(parts[1], 16) / Math.pow(16, parts[1].length) * sign; 81 | } else { 82 | val = parseInt(parts[0], 16); 83 | } 84 | return val; 85 | } 86 | 87 | function dat2str(dat) { 88 | if (dat.indexOf(0) >= 0) 89 | dat = dat.slice(0, dat.indexOf(0)); 90 | return new TextDecoder().decode(dat); 91 | } 92 | 93 | function str2dat(str) { 94 | let encoder = new TextEncoder(); 95 | return encoder.encode(str); 96 | } 97 | 98 | function val2hex(val, fixed=4, prefix=false, upper=false, float=false) { 99 | let sign = 1; 100 | if (val < 0) { 101 | sign = -1; 102 | val = -val; 103 | } 104 | let str = upper ? val.toString(16).toUpperCase() : val.toString(16); 105 | let arr = str.split('.'); 106 | if (arr[0].length < fixed) 107 | arr[0] = '0'.repeat(fixed - arr[0].length) + arr[0]; 108 | if (prefix) 109 | arr[0] = '0x' + arr[0]; 110 | if (sign == -1) 111 | arr[0] = '-' + arr[0]; 112 | if (float && arr.length == 1) 113 | arr.push('0'); 114 | return arr.join('.'); 115 | } 116 | 117 | function parse_bigint(str) { 118 | if (str.startsWith('-')) // handles format: '-0x...' 119 | return -BigInt(str.slice(1)); 120 | else 121 | return BigInt(str); 122 | } 123 | 124 | // list: ['x', 'y'] 125 | // map: {'rotation': 'r'} 126 | function cpy(dst, src, list, map = {}) { 127 | for (let i of list) { 128 | if (i in src) 129 | dst[i] = src[i]; 130 | } 131 | for (let i in map) { 132 | if (i in src) 133 | dst[map[i]] = src[i]; 134 | } 135 | } 136 | 137 | class Queue { 138 | constructor() { 139 | this.fifo = []; 140 | this.wakeup = null; 141 | } 142 | 143 | put(t) { 144 | this.fifo.push(t); 145 | if (this.wakeup) 146 | this.wakeup(); 147 | } 148 | 149 | async get(timeout=null) { 150 | if (this.fifo.length) 151 | return this.fifo.shift(); 152 | if (timeout == 0) 153 | return null; 154 | 155 | let p = new Promise(resolve => { this.wakeup = resolve; }); 156 | let t; 157 | if (timeout) 158 | t = setTimeout(() => { this.wakeup(); }, timeout, null); // unit: ms 159 | 160 | await p; 161 | 162 | this.wakeup = null; 163 | if (timeout) 164 | clearTimeout(t); 165 | if (this.fifo.length) 166 | return this.fifo.shift(); 167 | return null; 168 | } 169 | 170 | // now some utilities: 171 | size() { 172 | return this.fifo.length; 173 | } 174 | flush() { 175 | this.fifo = []; 176 | if (this.wakeup) 177 | this.wakeup(); 178 | this.wakeup = null; 179 | } 180 | } 181 | 182 | function download_url(data, fileName) { 183 | var a; 184 | a = document.createElement('a'); 185 | a.href = data; 186 | a.download = fileName; 187 | document.body.appendChild(a); 188 | a.style = 'display: none'; 189 | a.click(); 190 | a.remove(); 191 | }; 192 | 193 | function download(data, fileName='dat.bin', mimeType='application/octet-stream') { 194 | var blob, url; 195 | blob = new Blob([data], {type: mimeType}); 196 | url = window.URL.createObjectURL(blob); 197 | download_url(url, fileName); 198 | setTimeout(function() { return window.URL.revokeObjectURL(url); }, 1000); 199 | }; 200 | 201 | function escape_html(unsafe) { 202 | return unsafe 203 | .replace(/&/g, "&") 204 | .replace(//g, ">") 206 | .replace(/"/g, """) 207 | .replace(/'/g, "'"); 208 | } 209 | 210 | function readable_size(bytes, fixed=3, si=true) { 211 | var thresh = si ? 1000 : 1024; 212 | if(Math.abs(bytes) < thresh) { 213 | return bytes + ' B'; 214 | } 215 | var units = si 216 | ? ['kB','MB','GB','TB','PB','EB','ZB','YB'] 217 | : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']; 218 | var u = -1; 219 | do { 220 | bytes /= thresh; 221 | ++u; 222 | } while(Math.abs(bytes) >= thresh && u < units.length - 1); 223 | return bytes.toFixed(fixed)+' '+units[u]; 224 | } 225 | 226 | function readable_float(num, double=false) { 227 | if (!isFinite(num)) 228 | return num.toString(); 229 | let fixed = double ? 18 : 9; 230 | let n = num.toPrecision(double ? 16 : 7); 231 | if (n.indexOf('e') != -1) 232 | return n; 233 | let [int_part, dec_part = '0'] = n.split('.'); 234 | dec_part = dec_part.padEnd(fixed, '0'); 235 | n = `${int_part}.${dec_part}`; 236 | 237 | for (let i = 0; i < fixed / 3; i++) { 238 | if (n.endsWith('000')) 239 | n = n.slice(0, n.length - 3); 240 | else 241 | break; 242 | } 243 | if (n.endsWith('.')) 244 | n += '0'; 245 | return n; 246 | } 247 | 248 | async function blob2dat(blob) { 249 | let ret; 250 | await new Promise(resolve => { 251 | new Response(blob).arrayBuffer().then(buf => { 252 | ret = new Uint8Array(buf); 253 | resolve(); 254 | }); 255 | }); 256 | return ret; 257 | } 258 | 259 | function compare_dat(a, b) { 260 | if (a.length !== b.length) 261 | return -1; 262 | for (let i = 0; i < a.length; i++) { 263 | if (a[i] !== b[i]) 264 | return i; 265 | } 266 | return null; 267 | } 268 | 269 | function dat_append(dat0, dat1) { 270 | let dat = new Uint8Array(dat0.length + dat1.length); 271 | dat.set(dat0); 272 | dat.set(dat1, dat0.length); 273 | return dat; 274 | } 275 | 276 | 277 | // modbus crc 278 | const crc16_table = [ 279 | 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 280 | 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 281 | 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 282 | 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 283 | 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 284 | 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 285 | 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 286 | 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 287 | 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, 288 | 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 289 | 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, 290 | 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 291 | 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, 292 | 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, 293 | 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, 294 | 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, 295 | 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, 296 | 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, 297 | 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 298 | 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 299 | 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 300 | 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 301 | 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 302 | 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 303 | 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 304 | 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, 305 | 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 306 | 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, 307 | 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 308 | 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, 309 | 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 310 | 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040 311 | ]; 312 | 313 | function crc16(data, crc_val=0xffff) { 314 | for (let i = 0; i < data.length; i++) { 315 | let tmp = (data[i] ^ crc_val) & 0xff; 316 | crc_val = (crc_val >> 8) ^ crc16_table[tmp]; 317 | } 318 | return crc_val; 319 | } 320 | 321 | 322 | export { 323 | sleep, read_file, load_img, date2num, timestamp, 324 | sha256, aes256, 325 | dat2hex, hex2dat, hex2float, dat2str, str2dat, val2hex, parse_bigint, 326 | cpy, Queue, 327 | download, 328 | escape_html, readable_size, readable_float, 329 | blob2dat, compare_dat, dat_append, crc16 330 | }; 331 | -------------------------------------------------------------------------------- /configs/cdfoc-v6.json: -------------------------------------------------------------------------------- 1 | { 2 | "reg": { 3 | // fmt: [c]: string, b: int8_t, B: uint8_t, h: int16_t, H: uint16_t, i: int32_t, I: uint32_t, f: float 4 | // show: 0: normal, 1: hex, 2: bytes 5 | "list": [ 6 | [ 0x0000, 2, "H", 1, "magic_code", "Magic code: 0xcdcd" ], 7 | [ 0x0002, 2, "H", 1, "conf_ver", "Config version" ], 8 | [ 0x0004, 1, "B", 1, "conf_from", "0: default config, 1: all from flash, 2: partly from flash" ], 9 | [ 0x0005, 1, "B", 0, "do_reboot", "1: reboot to bl, 2: reboot to app" ], 10 | [ 0x0007, 1, "b", 0, "save_conf", "Write 1 to save current config to flash" ], 11 | 12 | [ 0x000c, 1, "B", 1, "bus_cfg_mac", "RS-485 port id, range: 0~254" ], 13 | [ 0x0010, 4, "I", 0, "bus_cfg_baud_l", "RS-485 baud rate for first byte" ], 14 | [ 0x0014, 4, "I", 0, "bus_cfg_baud_h", "RS-485 baud rate for follow bytes" ], 15 | [ 0x0018, 2, "[B]", 1, "bus_cfg_filter_m", "Multicast address" ], 16 | [ 0x001a, 1, "B", 0, "bus_cfg_mode", "0: Arbitration, 1: Break Sync" ], 17 | [ 0x001c, 2, "H", 0, "bus_cfg_tx_permit_len", "Allow send wait time" ], 18 | [ 0x001e, 2, "H", 0, "bus_cfg_max_idle_len", "Max idle wait time for BS mode" ], 19 | [ 0x0020, 1, "B", 0, "bus_cfg_tx_pre_len", " Active TX_EN before TX" ], 20 | 21 | [ 0x0024, 1, "b", 0, "dbg_en", "1: Report debug message to host, 0: do not report" ], 22 | 23 | [ 0x002c, 4, "f", 0, "pid_pos_kp", "" ], 24 | [ 0x0030, 4, "f", 0, "pid_pos_ki", "" ], 25 | [ 0x0038, 4, "f", 0, "pid_pos_out_min", "" ], 26 | [ 0x003c, 4, "f", 0, "pid_pos_out_max", "" ], 27 | 28 | [ 0x0070, 4, "f", 0, "pid_speed_kp", "" ], 29 | [ 0x0074, 4, "f", 0, "pid_speed_ki", "" ], 30 | [ 0x007c, 4, "f", 0, "pid_speed_out_min", "" ], 31 | [ 0x0080, 4, "f", 0, "pid_speed_out_max", "" ], 32 | 33 | [ 0x00b4, 4, "f", 0, "pid_i_sq_kp", "" ], 34 | [ 0x00b8, 4, "f", 0, "pid_i_sq_ki", "" ], 35 | [ 0x00c0, 4, "f", 0, "pid_i_sq_out_min", "" ], 36 | [ 0x00c4, 4, "f", 0, "pid_i_sq_out_max", "" ], 37 | 38 | [ 0x00f8, 4, "f", 0, "pid_i_sd_kp", "" ], 39 | [ 0x00fc, 4, "f", 0, "pid_i_sd_ki", "" ], 40 | [ 0x0104, 4, "f", 0, "pid_i_sd_out_min", "" ], 41 | [ 0x0108, 4, "f", 0, "pid_i_sd_out_max", "" ], 42 | 43 | [ 0x0144, 1, "B", 0, "motor_poles", "Motor poles" ], 44 | [ 0x0145, 1, "b", 0, "motor_wire_swap", "Software swaps motor wiring" ], 45 | [ 0x0146, 2, "H", 1, "bias_encoder", "Offset for encoder value" ], 46 | [ 0x0148, 4, "i", 0, "bias_pos", "Offset for pos value" ], 47 | 48 | [ 0x014c, 4, "H,B2", 1, "qxchg_mcast", "Offset and size for quick-exchange multicast" ], 49 | [ 0x0150, 20, "{H,B2}", 1, "qxchg_set", "Config the write data components for quick-exchange channel" ], 50 | [ 0x0164, 20, "{H,B2}", 1, "qxchg_ret", "Config the return data components for quick-exchange channel" ], 51 | 52 | [ 0x018c, 1, "B", 1, "dbg_str_msk", "Config which debug data to be send" ], 53 | 54 | [ 0x0196, 1, "B", 1, "dbg_raw_msk", "Config which raw debug data to be send" ], 55 | [ 0x0198, 24, "{H,B2}", 1, "dbg_raw[0]", "Config raw debug for current loop" ], 56 | [ 0x01b0, 24, "{H,B2}", 1, "dbg_raw[1]", "Config raw debug for speed loop" ], 57 | [ 0x01c8, 24, "{H,B2}", 1, "dbg_raw[2]", "Config raw debug for position loop" ], 58 | [ 0x01e0, 24, "{H,B2}", 1, "dbg_raw[3]", "Config raw debug for position plan" ], 59 | 60 | [ 0x01f8, 4, "i", 1, "tc_pos", "Set target position" ], 61 | [ 0x01fc, 4, "I", 1, "tc_speed", "Set target speed" ], 62 | [ 0x0200, 4, "I", 1, "tc_accel", "Set target accel" ], 63 | 64 | [ 0x020c, 4, "f", 0, "cali_angle_elec", "Calibration mode angle" ], 65 | [ 0x0210, 4, "f", 0, "cali_current", "Calibration mode current" ], 66 | [ 0x0214, 4, "f", 0, "cali_angle_speed_tgt", "Calibration mode speed" ], 67 | [ 0x0218, 1, "B", 0, "cali_run", "0: stopped, write 1 start calibration" ], 68 | 69 | [ 0x0219, 1, "b", 0, "cali_encoder_en", "" ], 70 | [ 0x021a, 1, "b", 0, "anticogging_en", "" ], 71 | [ 0x021c, 8, "[f]", 0, "anticogging_max_val", "" ], 72 | [ 0x0224, 4, "f", 0, "nominal_voltage", "" ], 73 | [ 0x0228, 2, "H", 0, "tc_max_err", "Limit position error" ], 74 | [ 0x022a, 2, "H", 0, "ntc_b", "" ], 75 | [ 0x022c, 4, "I", 0, "ntc_r25", "" ], 76 | [ 0x0230, 1, "B", 0, "temperature_warn", "" ], 77 | [ 0x0231, 1, "B", 0, "temperature_err", "" ], 78 | [ 0x0232, 1, "B", 0, "voltage_min", "" ], 79 | [ 0x0233, 1, "B", 0, "voltage_max", "" ], 80 | 81 | [ 0x0240, 1, "B", 0, "state", "0: stop, 1: calibrate, 2: cur loop, 3: speed loop, 4: pos loop, 5: t_curve" ], 82 | [ 0x0242, 2, "H", 1, "err_flag", "" ], 83 | 84 | [ 0x0244, 4, "i", 1, "cal_pos", "pos loop target" ], 85 | [ 0x0248, 4, "f", 1, "cal_speed", "speed loop target" ], 86 | [ 0x024c, 4, "i", 0, "cal_current", "cur loop target" ], 87 | [ 0x0250, 4, "f", 0, "cal_v_sq", "v_sq info" ], 88 | [ 0x0254, 4, "f", 0, "cal_v_sd", "v_sd info" ], 89 | 90 | [ 0x0258, 2, "H", 1, "ori_encoder", "Origin encoder value" ], 91 | [ 0x025c, 4, "i", 1, "ori_pos", "sen_pos before add offset" ], 92 | 93 | [ 0x0260, 4, "f", 1, "delta_encoder", "Encoder value delta" ], 94 | [ 0x0264, 2, "H", 1, "nob_encoder", "Encoder value before add bias" ], 95 | [ 0x0266, 2, "H", 1, "sen_encoder", "Encoder value filtered" ], 96 | [ 0x0268, 4, "i", 1, "sen_pos", "multiturn + sen_encoder data" ], 97 | [ 0x026c, 4, "f", 1, "sen_speed", "delta_encoder filtered" ], 98 | [ 0x0270, 4, "f", 0, "sen_i_sq", "i_sq from adc" ], 99 | [ 0x0274, 4, "f", 0, "sen_i_sd", "i_sd from adc" ], 100 | [ 0x0278, 4, "f", 0, "sen_angle_elec", "Get electric angle from sen_encoder" ], 101 | 102 | [ 0x027c, 4, "I", 0, "loop_cnt", "Increase at current loop, for raw dbg" ], 103 | 104 | //--------------- Follows are not writable: ------------------- 105 | [ 0x0284, 1, "B", 0, "tc_state", "t_curve: 0: stop, 1: run, 2: tailer" ], 106 | [ 0x0288, 4, "f", 0, "tc_vc", "Motor current speed" ], 107 | [ 0x028c, 4, "f", 0, "tc_ac", "Motor current accel" ], 108 | 109 | [ 0x0290, 1, "B", 0, "adc_sel", "" ], 110 | [ 0x0292, 6, "[h]", 0, "sen_i", "" ], 111 | [ 0x0298, 6, "[h]", 0, "pwm_dbg0", "" ], 112 | [ 0x029e, 6, "[h]", 0, "pwm_dbg1", "" ], 113 | [ 0x02a4, 6, "[h]", 0, "pwm_uvw", "" ], 114 | 115 | [ 0x02ac, 4, "f", 0, "sen_i_sq_avg", "" ], 116 | [ 0x02b0, 4, "f", 0, "cal_v_sq_avg", "" ], 117 | [ 0x02bc, 4, "f", 0, "sen_speed_avg", "" ], 118 | [ 0x02c0, 4, "f", 0, "sen_rpm_avg", "" ], 119 | [ 0x02c4, 4, "f", 0, "bus_voltage", "" ], 120 | [ 0x02c8, 4, "f", 0, "temperature", "" ], 121 | [ 0x02cc, 4, "f", 0, "tc_vc_avg", "" ], 122 | [ 0x02d0, 4, "f", 0, "cali_angle_speed", "" ] 123 | ], 124 | 125 | "reg_r": [["magic_code"],["conf_ver","save_conf"],["bus_cfg_mac","bus_cfg_baud_h"],["bus_cfg_filter_m","bus_cfg_tx_pre_len"],["dbg_en"],["pid_pos_kp","pid_pos_ki"],["pid_pos_out_min","pid_pos_out_max"],["pid_speed_kp"],["pid_speed_ki"],["pid_speed_out_min","pid_speed_out_max"],["pid_i_sq_kp","pid_i_sq_ki"],["pid_i_sq_out_min","pid_i_sq_out_max"],["pid_i_sd_kp","pid_i_sd_ki"],["pid_i_sd_out_min","pid_i_sd_out_max"],["motor_poles","bias_pos"],["qxchg_mcast"],["qxchg_set","qxchg_ret"],["dbg_str_msk"],["dbg_raw_msk","dbg_raw[3]"],["tc_pos","tc_accel"],["cali_angle_elec","cali_run"],["cali_encoder_en","anticogging_max_val"],["nominal_voltage","tc_max_err"],["ntc_b","ntc_r25"],["temperature_warn","voltage_max"],["state","cal_current"],["cal_v_sq","cal_v_sd"],["ori_encoder","sen_speed"],["sen_i_sq","sen_angle_elec"],["loop_cnt","tc_ac"],["adc_sel"],["sen_i","pwm_uvw"],["sen_i_sq_avg","cal_v_sq_avg"],["sen_speed_avg","sen_rpm_avg"],["bus_voltage","temperature"],["tc_vc_avg","cali_angle_speed"]], 126 | "reg_w": [["conf_ver","conf_from"],["do_reboot"],["save_conf"],["bus_cfg_mac","bus_cfg_baud_h"],["bus_cfg_filter_m","bus_cfg_tx_pre_len"],["dbg_en"],["pid_pos_kp","pid_pos_ki"],["pid_pos_out_min","pid_pos_out_max"],["pid_speed_kp"],["pid_speed_ki"],["pid_speed_out_min","pid_speed_out_max"],["pid_i_sq_kp","pid_i_sq_ki"],["pid_i_sq_out_min","pid_i_sq_out_max"],["pid_i_sd_kp","pid_i_sd_ki"],["pid_i_sd_out_min","pid_i_sd_out_max"],["motor_poles"],["motor_wire_swap"],["bias_encoder"],["bias_pos"],["qxchg_mcast"],["qxchg_set","qxchg_ret"],["dbg_str_msk"],["dbg_raw_msk"],["dbg_raw[0]"],["dbg_raw[1]"],["dbg_raw[2]"],["dbg_raw[3]"],["tc_pos"],["tc_speed","tc_accel"],["cali_angle_elec"],["cali_current"],["cali_angle_speed_tgt"],["cali_run"],["cali_encoder_en","anticogging_en"],["anticogging_max_val"],["nominal_voltage"],["tc_max_err"],["ntc_b","ntc_r25"],["temperature_warn","voltage_max"],["state"],["err_flag"],["cal_pos"],["cal_speed"],["cal_current"]], 127 | "less_r": [["tc_pos"],["tc_speed","tc_accel"],["state","cal_current"]], 128 | "less_w": [["tc_pos"],["tc_speed","tc_accel"],["state"],["cal_speed"],["cal_current"]] 129 | }, 130 | 131 | "plot": { 132 | "mask": "dbg_raw_msk", 133 | "plots": [ 134 | { 135 | // "color": ["black", "red", "green"] 136 | "fmt": "H1.fffffffH", 137 | "label": ["N", "sq in", "sd in", "sq tgt", "sq i_term", "sd i_term", "sq cal", "sd cal", "sen_encoder"], 138 | "cal": { 139 | "sq_err": "_d[1].at(-1) - _d[3].at(-1)", // data1 - data3 140 | "sq_err_avg": " \ 141 | let a = 0; \ 142 | for (let i = 0; i < 5; i++) \ 143 | a += _d[9].at(-1-i); \ 144 | return a / 5;", 145 | "sq_err_avg2": " \ 146 | if (_d[9]._f == undefined) \ 147 | _d[9]._f = 0; \ 148 | let err = _d[9].at(-1) - _d[9]._f; \ 149 | _d[9]._f += err * 0.02; \ 150 | return _d[9]._f;" 151 | }, 152 | "fft": { 153 | "sample_rate": 20751.953, 154 | "size": 4096 155 | } 156 | }, { 157 | "fmt": "H5.fffiHf", 158 | "label": ["N", "sen_speed", "speed target", "i_term", "cal_current", "sen_encoder", "sen_speed_avg"], 159 | "cal": { 160 | "speed_err2i": "_d[2].at(-1) - _d[1].at(-1)", 161 | "speed_err2p": "_d[2].at(-1) - _d[6].at(-1)", 162 | "p term": "_d[4].at(-1) - _d[3].at(-1)" 163 | } 164 | }, { 165 | "fmt": "H25.iiffff", 166 | "label": ["N", "pos_cur", "pos target", "i_term", "cal_speed", "tc_vc_avg", "sen_speed_avg"], 167 | "cal": { 168 | "pos_err": "_d[2].at(-1) - _d[1].at(-1)", 169 | "p term": "_d[7].at(-1) * 50" 170 | } 171 | }, { 172 | "fmt": "H25.Biiff", 173 | "label": ["N", "tc_state", "tc_poc", "cal_pos", "tc_vc", "tc_ac"], 174 | } 175 | ] 176 | }, 177 | 178 | "iap": { "reboot": 0x0005, "keep_bl": 0x0006 } 179 | } 180 | -------------------------------------------------------------------------------- /configs/cdfoc-v6-sensorless.json: -------------------------------------------------------------------------------- 1 | { 2 | "reg": { 3 | // fmt: [c]: string, b: int8_t, B: uint8_t, h: int16_t, H: uint16_t, i: int32_t, I: uint32_t, f: float 4 | // show: 0: normal, 1: hex, 2: bytes 5 | "list": [ 6 | [ 0x0000, 2, "H", 1, "magic_code", "Magic code: 0xcdcd" ], 7 | [ 0x0002, 2, "H", 1, "conf_ver", "Config version" ], 8 | [ 0x0004, 1, "B", 1, "conf_from", "0: default config, 1: all from flash, 2: partly from flash" ], 9 | [ 0x0005, 1, "B", 0, "do_reboot", "1: reboot to bl, 2: reboot to app" ], 10 | [ 0x0007, 1, "b", 0, "save_conf", "Write 1 to save current config to flash" ], 11 | 12 | [ 0x000c, 1, "B", 1, "bus_cfg_mac", "RS-485 port id, range: 0~254" ], 13 | [ 0x0010, 4, "I", 0, "bus_cfg_baud_l", "RS-485 baud rate for first byte" ], 14 | [ 0x0014, 4, "I", 0, "bus_cfg_baud_h", "RS-485 baud rate for follow bytes" ], 15 | [ 0x0018, 2, "[B]", 1, "bus_cfg_filter_m", "Multicast address" ], 16 | [ 0x001a, 1, "B", 0, "bus_cfg_mode", "0: Arbitration, 1: Break Sync" ], 17 | [ 0x001c, 2, "H", 0, "bus_cfg_tx_permit_len", "Allow send wait time" ], 18 | [ 0x001e, 2, "H", 0, "bus_cfg_max_idle_len", "Max idle wait time for BS mode" ], 19 | [ 0x0020, 1, "B", 0, "bus_cfg_tx_pre_len", " Active TX_EN before TX" ], 20 | 21 | [ 0x0024, 1, "b", 0, "dbg_en", "1: Report debug message to host, 0: do not report" ], 22 | 23 | [ 0x002c, 4, "f", 0, "pid_pos_kp", "" ], 24 | [ 0x0030, 4, "f", 0, "pid_pos_ki", "" ], 25 | [ 0x0038, 4, "f", 0, "pid_pos_out_min", "" ], 26 | [ 0x003c, 4, "f", 0, "pid_pos_out_max", "" ], 27 | 28 | [ 0x0070, 4, "f", 0, "pid_speed_kp", "" ], 29 | [ 0x0074, 4, "f", 0, "pid_speed_ki", "" ], 30 | [ 0x007c, 4, "f", 0, "pid_speed_out_min", "" ], 31 | [ 0x0080, 4, "f", 0, "pid_speed_out_max", "" ], 32 | 33 | [ 0x00b4, 4, "f", 0, "pid_i_sq_kp", "" ], 34 | [ 0x00b8, 4, "f", 0, "pid_i_sq_ki", "" ], 35 | [ 0x00c0, 4, "f", 0, "pid_i_sq_out_min", "" ], 36 | [ 0x00c4, 4, "f", 0, "pid_i_sq_out_max", "" ], 37 | 38 | [ 0x00f8, 4, "f", 0, "pid_i_sd_kp", "" ], 39 | [ 0x00fc, 4, "f", 0, "pid_i_sd_ki", "" ], 40 | [ 0x0104, 4, "f", 0, "pid_i_sd_out_min", "" ], 41 | [ 0x0108, 4, "f", 0, "pid_i_sd_out_max", "" ], 42 | 43 | [ 0x0144, 1, "B", 0, "motor_poles", "Motor poles" ], 44 | [ 0x0145, 1, "b", 0, "motor_wire_swap", "Software swaps motor wiring" ], 45 | [ 0x0146, 2, "H", 1, "bias_encoder", "Offset for encoder value" ], 46 | [ 0x0148, 4, "i", 0, "bias_pos", "Offset for pos value" ], 47 | 48 | [ 0x014c, 4, "H,B2", 1, "qxchg_mcast", "Offset and size for quick-exchange multicast" ], 49 | [ 0x0150, 20, "{H,B2}", 1, "qxchg_set", "Config the write data components for quick-exchange channel" ], 50 | [ 0x0164, 20, "{H,B2}", 1, "qxchg_ret", "Config the return data components for quick-exchange channel" ], 51 | 52 | [ 0x018c, 1, "B", 1, "dbg_str_msk", "Config which debug data to be send" ], 53 | 54 | [ 0x0196, 1, "B", 1, "dbg_raw_msk", "Config which raw debug data to be send" ], 55 | [ 0x0198, 24, "{H,B2}", 1, "dbg_raw[0]", "Config raw debug for current loop" ], 56 | [ 0x01b0, 24, "{H,B2}", 1, "dbg_raw[1]", "Config raw debug for speed loop" ], 57 | [ 0x01c8, 24, "{H,B2}", 1, "dbg_raw[2]", "Config raw debug for position loop" ], 58 | [ 0x01e0, 24, "{H,B2}", 1, "dbg_raw[3]", "Config raw debug for position plan" ], 59 | 60 | [ 0x01f8, 4, "i", 1, "tc_pos", "Set target position" ], 61 | [ 0x01fc, 4, "I", 1, "tc_speed", "Set target speed" ], 62 | [ 0x0200, 4, "I", 1, "tc_accel", "Set target accel" ], 63 | 64 | [ 0x020c, 4, "f", 0, "cali_angle_elec", "Calibration mode angle" ], 65 | [ 0x0210, 4, "f", 0, "cali_current", "Calibration mode current" ], 66 | [ 0x0214, 4, "f", 0, "cali_angle_speed_tgt", "Calibration mode speed" ], 67 | [ 0x0218, 1, "B", 0, "cali_run", "0: stopped, write 1 start calibration" ], 68 | 69 | [ 0x0219, 1, "b", 0, "cali_encoder_en", "" ], 70 | [ 0x021a, 1, "b", 0, "anticogging_en", "" ], 71 | [ 0x021c, 8, "[f]", 0, "anticogging_max_val", "" ], 72 | [ 0x0224, 4, "f", 0, "nominal_voltage", "" ], 73 | [ 0x0228, 2, "H", 0, "tc_max_err", "Limit position error" ], 74 | [ 0x022a, 2, "H", 0, "ntc_b", "" ], 75 | [ 0x022c, 4, "I", 0, "ntc_r25", "" ], 76 | [ 0x0230, 1, "B", 0, "temperature_warn", "" ], 77 | [ 0x0231, 1, "B", 0, "temperature_err", "" ], 78 | [ 0x0232, 1, "B", 0, "voltage_min", "" ], 79 | [ 0x0233, 1, "B", 0, "voltage_max", "" ], 80 | 81 | [ 0x0240, 4, "f", 0, "smo_v_alpha_real", "Motor voltage [V]" ], 82 | [ 0x0244, 4, "f", 0, "smo_v_beta_real", "" ], 83 | [ 0x0248, 4, "f", 0, "smo_i_alpha_real", "Real current [A]" ], 84 | [ 0x024c, 4, "f", 0, "smo_i_beta_real", "" ], 85 | [ 0x0250, 4, "f", 0, "smo_i_alpha", "Estimated current [A]" ], 86 | [ 0x0254, 4, "f", 0, "smo_i_beta", "" ], 87 | [ 0x0258, 4, "f", 0, "smo_e_alpha", "Back-EMF [V]" ], 88 | [ 0x025c, 4, "f", 0, "smo_e_beta", "" ], 89 | [ 0x0260, 4, "f", 0, "smo_l", "Winding inductance [H]" ], 90 | [ 0x0264, 4, "f", 0, "smo_r", "Winding resistance [Ohm]" ], 91 | [ 0x0268, 4, "f", 0, "smo_gamma", "" ], 92 | [ 0x026c, 4, "f", 0, "smo_eps", "" ], 93 | [ 0x02a0, 4, "f", 0, "pll_theta", "[rad]" ], 94 | [ 0x02a4, 4, "f", 0, "pll_omega", "[rad/s]" ], 95 | [ 0x02a8, 4, "f", 0, "pll_i_term", "" ], 96 | [ 0x02ac, 4, "f", 0, "pll_kp", "" ], 97 | [ 0x02b0, 4, "f", 0, "pll_ki", "" ], 98 | [ 0x02bc, 4, "f", 0, "pll__atan2", "[rad]" ], 99 | [ 0x02d0, 1, "b", 0, "sl_start", "0: idle, 1: cw, -1: ccw" ], 100 | [ 0x02d1, 1, "b", 0, "sl_state", "0: idle, 1: speed inc, 2: current dec, 3: closeloop, -1: err" ], 101 | 102 | [ 0x0340, 1, "B", 0, "state", "0: stop, 1: calibrate, 2: cur loop, 3: speed loop, 4: pos loop, 5: t_curve" ], 103 | [ 0x0342, 2, "H", 1, "err_flag", "" ], 104 | 105 | [ 0x0344, 4, "i", 1, "cal_pos", "pos loop target" ], 106 | [ 0x0348, 4, "f", 1, "cal_speed", "speed loop target" ], 107 | [ 0x034c, 4, "i", 0, "cal_current", "cur loop target" ], 108 | [ 0x0350, 4, "f", 0, "cal_v_sq", "v_sq info" ], 109 | [ 0x0354, 4, "f", 0, "cal_v_sd", "v_sd info" ], 110 | 111 | [ 0x0358, 2, "H", 1, "ori_encoder", "Origin encoder value" ], 112 | [ 0x035c, 4, "i", 1, "ori_pos", "sen_pos before add offset" ], 113 | 114 | [ 0x0360, 4, "f", 1, "delta_encoder", "Encoder value delta" ], 115 | [ 0x0364, 2, "H", 1, "nob_encoder", "Encoder value before add bias" ], 116 | [ 0x0366, 2, "H", 1, "sen_encoder", "Encoder value filtered" ], 117 | [ 0x0368, 4, "i", 1, "sen_pos", "multiturn + sen_encoder data" ], 118 | [ 0x036c, 4, "f", 1, "sen_speed", "delta_encoder filtered" ], 119 | [ 0x0370, 4, "f", 0, "sen_i_sq", "i_sq from adc" ], 120 | [ 0x0374, 4, "f", 0, "sen_i_sd", "i_sd from adc" ], 121 | [ 0x0378, 4, "f", 0, "sen_angle_elec", "Get electric angle from sen_encoder" ], 122 | 123 | [ 0x037c, 4, "I", 0, "loop_cnt", "Increase at current loop, for raw dbg" ], 124 | 125 | //--------------- Follows are not writable: ------------------- 126 | [ 0x0384, 1, "B", 0, "tc_state", "t_curve: 0: stop, 1: run, 2: tailer" ], 127 | [ 0x0388, 4, "f", 0, "tc_vc", "Motor current speed" ], 128 | [ 0x038c, 4, "f", 0, "tc_ac", "Motor current accel" ], 129 | 130 | [ 0x0390, 1, "B", 0, "adc_sel", "" ], 131 | [ 0x0392, 6, "[h]", 0, "sen_i", "" ], 132 | [ 0x0398, 6, "[h]", 0, "pwm_dbg0", "" ], 133 | [ 0x039e, 6, "[h]", 0, "pwm_dbg1", "" ], 134 | [ 0x03a4, 6, "[h]", 0, "pwm_uvw", "" ], 135 | 136 | [ 0x03ac, 4, "f", 0, "sen_i_sq_avg", "" ], 137 | [ 0x03b0, 4, "f", 0, "cal_v_sq_avg", "" ], 138 | [ 0x03bc, 4, "f", 0, "sen_speed_avg", "" ], 139 | [ 0x03c0, 4, "f", 0, "sen_rpm_avg", "" ], 140 | [ 0x03c4, 4, "f", 0, "bus_voltage", "" ], 141 | [ 0x03c8, 4, "f", 0, "temperature", "" ], 142 | [ 0x03cc, 4, "f", 0, "tc_vc_avg", "" ], 143 | [ 0x03d0, 4, "f", 0, "cali_angle_speed", "" ] 144 | ], 145 | 146 | "reg_r": [["magic_code"],["conf_ver","save_conf"],["bus_cfg_mac","bus_cfg_baud_h"],["bus_cfg_filter_m","bus_cfg_tx_pre_len"],["dbg_en"],["pid_pos_kp","pid_pos_ki"],["pid_pos_out_min","pid_pos_out_max"],["pid_speed_kp"],["pid_speed_ki"],["pid_speed_out_min","pid_speed_out_max"],["pid_i_sq_kp","pid_i_sq_ki"],["pid_i_sq_out_min","pid_i_sq_out_max"],["pid_i_sd_kp","pid_i_sd_ki"],["pid_i_sd_out_min","pid_i_sd_out_max"],["motor_poles","bias_pos"],["qxchg_mcast"],["qxchg_set","qxchg_ret"],["dbg_str_msk"],["dbg_raw_msk","dbg_raw[3]"],["tc_pos","tc_accel"],["cali_angle_elec","cali_run"],["cali_encoder_en","anticogging_max_val"],["nominal_voltage","tc_max_err"],["ntc_b","ntc_r25"],["temperature_warn","voltage_max"],["smo_v_alpha_real","smo_e_beta"],["smo_l","smo_eps"],["pll_theta","pll_i_term"],["pll_kp","pll_ki"],["pll__atan2"],["sl_start","sl_state"],["state","cal_current"],["cal_v_sq","cal_v_sd"],["ori_encoder","sen_speed"],["sen_i_sq","sen_angle_elec"],["loop_cnt","tc_ac"],["adc_sel"],["sen_i","pwm_uvw"],["sen_i_sq_avg","cal_v_sq_avg"],["sen_speed_avg","sen_rpm_avg"],["bus_voltage","temperature"],["tc_vc_avg","cali_angle_speed"]], 147 | "reg_w": [["conf_ver","conf_from"],["do_reboot"],["save_conf"],["bus_cfg_mac","bus_cfg_baud_h"],["bus_cfg_filter_m","bus_cfg_tx_pre_len"],["dbg_en"],["pid_pos_kp","pid_pos_ki"],["pid_pos_out_min","pid_pos_out_max"],["pid_speed_kp"],["pid_speed_ki"],["pid_speed_out_min","pid_speed_out_max"],["pid_i_sq_kp","pid_i_sq_ki"],["pid_i_sq_out_min","pid_i_sq_out_max"],["pid_i_sd_kp","pid_i_sd_ki"],["pid_i_sd_out_min","pid_i_sd_out_max"],["motor_poles"],["motor_wire_swap"],["bias_encoder"],["bias_pos"],["qxchg_mcast"],["qxchg_set","qxchg_ret"],["dbg_str_msk"],["dbg_raw_msk"],["dbg_raw[0]"],["dbg_raw[1]"],["dbg_raw[2]"],["dbg_raw[3]"],["tc_pos"],["tc_speed","tc_accel"],["cali_angle_elec"],["cali_current"],["cali_angle_speed_tgt"],["cali_run"],["cali_encoder_en","anticogging_en"],["anticogging_max_val"],["nominal_voltage"],["tc_max_err"],["ntc_b","ntc_r25"],["temperature_warn","voltage_max"],["smo_l","smo_eps"],["pll_kp","pll_ki"],["sl_start"],["state"],["err_flag"],["cal_pos"],["cal_speed"],["cal_current"]], 148 | "less_r": [["tc_pos"],["tc_speed","tc_accel"],["sl_start","sl_state"],["state","cal_current"]], 149 | "less_w": [["tc_pos"],["tc_speed","tc_accel"],["sl_start"],["state"],["cal_speed"],["cal_current"]] 150 | }, 151 | 152 | "plot": { 153 | "mask": "dbg_raw_msk", 154 | "plots": [ 155 | { 156 | // "color": ["black", "red", "green"] 157 | "fmt": "H1.fffffffH", 158 | "label": ["N", "sq in", "sd in", "sq tgt", "sq i_term", "sd i_term", "sq cal", "sd cal", "sen_encoder"], 159 | //"fmt": "H1.ffffffffff", 160 | //"label": ["N", "v_a_real", "v_b_real", "i_a_real", "i_b_real", "i_a", "i_b", "e_a", "e_b", "atan2", "sen_elec"], 161 | "cal": { 162 | "sq_err": "_d[1].at(-1) - _d[3].at(-1)", // data1 - data3 163 | "sq_err_avg": " \ 164 | let a = 0; \ 165 | for (let i = 0; i < 5; i++) \ 166 | a += _d[9].at(-1-i); \ 167 | return a / 5;", 168 | "sq_err_avg2": " \ 169 | if (_d[9]._f == undefined) \ 170 | _d[9]._f = 0; \ 171 | let err = _d[9].at(-1) - _d[9]._f; \ 172 | _d[9]._f += err * 0.02; \ 173 | return _d[9]._f;" 174 | }, 175 | "fft": { 176 | "sample_rate": 20751.953, 177 | "size": 4096 178 | } 179 | }, { 180 | "fmt": "H5.fffiHf", 181 | "label": ["N", "sen_speed", "speed target", "i_term", "cal_current", "sen_encoder", "sen_speed_avg"], 182 | "cal": { 183 | "speed_err2i": "_d[2].at(-1) - _d[1].at(-1)", 184 | "speed_err2p": "_d[2].at(-1) - _d[6].at(-1)", 185 | "p term": "_d[4].at(-1) - _d[3].at(-1)" 186 | } 187 | }, { 188 | "fmt": "H25.iiffff", 189 | "label": ["N", "pos_cur", "pos target", "i_term", "cal_speed", "tc_vc_avg", "sen_speed_avg"], 190 | "cal": { 191 | "pos_err": "_d[2].at(-1) - _d[1].at(-1)", 192 | "p term": "_d[7].at(-1) * 50" 193 | } 194 | }, { 195 | "fmt": "H25.Biiff", 196 | "label": ["N", "tc_state", "tc_poc", "cal_pos", "tc_vc", "tc_ac"], 197 | } 198 | ] 199 | }, 200 | 201 | "iap": { "reboot": 0x0005, "keep_bl": 0x0006 } 202 | } 203 | -------------------------------------------------------------------------------- /html/plugins/iap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | import { L } from '../utils/lang.js' 8 | import { sleep, escape_html, date2num, val2hex, dat2str, dat2hex, hex2dat, read_file, download, 9 | readable_size, blob2dat, compare_dat, dat_append, crc16 } from '../utils/helper.js'; 10 | import { CDWebSocket } from '../utils/cd_ws.js'; 11 | import { Idb } from '../utils/idb.js'; 12 | import { csa, alloc_port } from '../common.js'; 13 | 14 | 15 | let html = ` 16 |
    17 |

    IAP

    18 |
    19 | 20 | 26 | 31 | 32 | 33 |
    34 |
    35 | ${L('Progress')}: -- 36 |
    37 |
    `; 38 | 39 | 40 | async function flash_erase(addr, len) { 41 | let d = new Uint8Array(9); 42 | let dv = new DataView(d.buffer); 43 | d[0] = 0x2f; 44 | dv.setUint32(1, addr, true); 45 | dv.setUint32(5, len, true); 46 | 47 | csa.iap.proxy_sock.flush(); 48 | await csa.iap.proxy_sock.sendto({'dst': [csa.arg.tgt, 0x8], 'dat': d}, ['server', 'proxy']); 49 | console.log(`flash_erase wait ret, addr: ${val2hex(addr)}, len: ${(val2hex(len))}`); 50 | let ret = await csa.iap.proxy_sock.recvfrom(60000); 51 | console.log('flash_erase ret', ret); 52 | if (ret && ret[0].dat.length == 1 && (ret[0].dat[0] & 0xf) == 0) { 53 | return 0 54 | } else { 55 | console.log('flash_erase err'); 56 | return -1; 57 | } 58 | } 59 | 60 | async function flash_write_blk(addr, dat) { 61 | let d = new Uint8Array(5 + dat.length); 62 | let dv = new DataView(d.buffer); 63 | d[0] = 0x20; 64 | dv.setUint32(1, addr, true); 65 | d.set(dat, 5); 66 | 67 | csa.iap.proxy_sock.flush(); 68 | await csa.iap.proxy_sock.sendto({'dst': [csa.arg.tgt, 0x8], 'dat': d}, ['server', 'proxy']); 69 | console.log(`flash_write_blk wait ret, addr: ${val2hex(addr)}`); 70 | let ret = await csa.iap.proxy_sock.recvfrom(1000); 71 | console.log('flash_write_blk ret', ret); 72 | if (ret && ret[0].dat.length == 1 && (ret[0].dat[0] & 0xf) == 0) { 73 | return 0 74 | } else { 75 | console.log('flash_write_blk err'); 76 | return -1; 77 | } 78 | } 79 | 80 | async function flash_write(addr, dat, blk_size=128) { 81 | let cur = addr; 82 | let iap_progress_elm = document.getElementById('iap_progress'); 83 | while (!csa.iap.stop) { 84 | let size = Math.min(blk_size, dat.length - (cur - addr)); 85 | if (size == 0) 86 | return 0; 87 | let wdat = dat.slice(cur-addr, cur-addr+size); 88 | let ret = await flash_write_blk(cur, wdat); 89 | if (ret) 90 | return -1; 91 | cur += size; 92 | iap_progress_elm.innerText = `Write ${Math.round((cur - addr) / dat.length * 100)}%`; 93 | } 94 | return -2; 95 | } 96 | 97 | async function flash_read_blk(addr, len) { 98 | let d = new Uint8Array(6); 99 | let dv = new DataView(d.buffer); 100 | d[0] = 0x00; 101 | dv.setUint32(1, addr, true); 102 | d[5] = len; 103 | 104 | csa.iap.proxy_sock.flush(); 105 | await csa.iap.proxy_sock.sendto({'dst': [csa.arg.tgt, 0x8], 'dat': d}, ['server', 'proxy']); 106 | console.log(`flash_read_blk wait ret, addr: ${val2hex(addr)}, len: ${len}`); 107 | let ret = await csa.iap.proxy_sock.recvfrom(1000); 108 | console.log('flash_read_blk ret', ret); 109 | if (ret && (ret[0].dat[0] & 0xf) == 0 && ret[0].dat.length == len + 1) { 110 | return ret[0].dat.slice(1); 111 | } else { 112 | console.log('flash_read_blk err'); 113 | return null; 114 | } 115 | } 116 | 117 | async function flash_read(addr, len, blk_size=128) { 118 | let cur = addr; 119 | let buf = new Uint8Array(0); 120 | let iap_progress_elm = document.getElementById('iap_progress'); 121 | while (!csa.iap.stop) { 122 | let size = Math.min(blk_size, len - (cur - addr)); 123 | if (size == 0) 124 | return buf; 125 | let ret = await flash_read_blk(cur, size); 126 | if (ret == null) 127 | return null; 128 | buf = dat_append(buf, ret); 129 | cur += size; 130 | iap_progress_elm.innerText = `Read ${Math.round((cur - addr) / len * 100)}%`; 131 | } 132 | return null; 133 | } 134 | 135 | async function flash_read_crc(addr, len) { 136 | let d = new Uint8Array(9); 137 | let dv = new DataView(d.buffer); 138 | d[0] = 0x10; 139 | dv.setUint32(1, addr, true); 140 | dv.setUint32(5, len, true); 141 | 142 | csa.iap.proxy_sock.flush(); 143 | await csa.iap.proxy_sock.sendto({'dst': [csa.arg.tgt, 0x8], 'dat': d}, ['server', 'proxy']); 144 | console.log(`flash_read_crc ret, addr: ${val2hex(addr)}, len: ${val2hex(len)}`); 145 | let ret = await csa.iap.proxy_sock.recvfrom(1000); 146 | console.log('flash_read_crc', ret); 147 | if (ret && (ret[0].dat[0] & 0xf) == 0) { 148 | let ret_dv = new DataView(ret[0].dat.slice(1).buffer); 149 | return ret_dv.getUint16(0, true); 150 | } else { 151 | console.log('flash_read_crc err'); 152 | return null; 153 | } 154 | } 155 | 156 | async function keep_in_bl() { 157 | if (!('keep_bl' in csa.cfg.iap)) { 158 | console.log('keep_in_bl skip'); 159 | return 0; 160 | } 161 | let d = new Uint8Array([0x20, 0, 0, 1]); 162 | let dv = new DataView(d.buffer); 163 | dv.setUint16(1, csa.cfg.iap.keep_bl, true); 164 | 165 | csa.iap.proxy_sock.flush(); 166 | await csa.iap.proxy_sock.sendto({'dst': [csa.arg.tgt, 0x5], 'dat': d}, ['server', 'proxy']); 167 | console.log('keep_in_bl wait ret'); 168 | let ret = await csa.iap.proxy_sock.recvfrom(200); 169 | console.log('keep_in_bl ret', ret); 170 | if (ret && ret[0].dat.length == 1 && (ret[0].dat[0] & 0xf) == 0) { 171 | console.log('keep_in_bl succeeded'); 172 | return 0; 173 | } else { 174 | console.log('keep_in_bl err'); 175 | return -1; 176 | } 177 | } 178 | 179 | async function do_reboot(bl_args) { 180 | let d = new Uint8Array([0x20, 0, 0, bl_args]); 181 | let dv = new DataView(d.buffer); 182 | dv.setUint16(1, csa.cfg.iap.reboot, true); 183 | 184 | csa.iap.proxy_sock.flush(); 185 | await csa.iap.proxy_sock.sendto({'dst': [csa.arg.tgt, 0x5], 'dat': d}, ['server', 'proxy']); 186 | console.log('reboot wait ret'); 187 | let ret = await csa.iap.proxy_sock.recvfrom(200); 188 | console.log('reboot ret', ret); 189 | } 190 | 191 | 192 | async function stop_iap() { 193 | document.getElementById('iap_start').disabled = false; 194 | document.getElementById('iap_stop').disabled = csa.iap.stop = true; 195 | } 196 | 197 | async function do_iap() { 198 | document.getElementById('iap_start').disabled = true; 199 | document.getElementById('iap_stop').disabled = csa.iap.stop = false; 200 | document.getElementById('iap_epoch').innerText = ''; 201 | document.getElementById('iap_progress').innerText = '--'; 202 | 203 | let path = document.getElementById('iap_path').value; 204 | let check = document.getElementById('iap_check').value; 205 | let action = document.getElementById('iap_action').value; 206 | 207 | if (!path && action != 'bl') { 208 | alert('path empty'); 209 | stop_iap(); 210 | return; 211 | } 212 | 213 | let msg = null; 214 | if (action != "bl") { 215 | csa.cmd_sock.flush(); 216 | await csa.cmd_sock.sendto({'action': 'get_ihex', 'path': path}, ['server', 'iap']); 217 | msg = await csa.cmd_sock.recvfrom(20000); 218 | if (!msg || !msg[0].length) { 219 | alert('invalid ihex file'); 220 | stop_iap(); 221 | return; 222 | } 223 | console.log(`get_ihex:`, msg[0]); 224 | } 225 | 226 | let retry_cnt = 0; 227 | let reboot_cnt = 0; 228 | while (action.startsWith('bl') && !csa.iap.stop) { 229 | 230 | csa.iap.proxy_sock.flush(); 231 | await csa.iap.proxy_sock.sendto({'dst': [csa.arg.tgt, 0x1], 'dat': new Uint8Array([])}, ['server', 'proxy']); 232 | console.log('read info wait ret'); 233 | let ret = await csa.iap.proxy_sock.recvfrom(100); 234 | console.log('read info ret', ret); 235 | if (ret && ret[0].src[1] == 0x0001) { 236 | let s = dat2str(ret[0].dat); 237 | if (s.includes('(bl)')) { 238 | console.log(`found (bl): ${s}`); 239 | if (await keep_in_bl()) { 240 | document.getElementById('iap_progress').innerText = `keep_in_bl failed`; 241 | } else { 242 | document.getElementById('iap_progress').innerText = `keep_in_bl succeeded`; 243 | break; 244 | } 245 | } else { 246 | console.log('not found (bl), reboot'); 247 | document.getElementById('iap_progress').innerText = `Not found string "(bl)", reboot...`; 248 | await do_reboot(1); 249 | reboot_cnt++; 250 | } 251 | } else { 252 | let retry_str; 253 | switch (retry_cnt) { 254 | case 0: retry_str = '-'; break; 255 | case 1: retry_str = '\\'; break; 256 | case 2: retry_str = '|'; break; 257 | case 3: retry_str = '/'; break; 258 | } 259 | console.log('read info time out'); 260 | document.getElementById('iap_progress').innerHTML = 261 | `Try read info (${retry_str}) | try reboot: ${reboot_cnt}`; 262 | } 263 | if (++retry_cnt >= 4) 264 | retry_cnt = 0; 265 | } 266 | 267 | if (action == "flash" && !csa.iap.stop) { 268 | if (await keep_in_bl()) { 269 | document.getElementById('iap_progress').innerText = `keep_in_bl failed`; 270 | stop_iap(); 271 | return; 272 | } else { 273 | console.log(`keep_in_bl succeeded`); 274 | document.getElementById('iap_progress').innerText = `keep_in_bl succeeded`; 275 | } 276 | } 277 | 278 | for (let idx = 0; action != "bl" && idx < msg[0].length; idx++) { 279 | if (csa.iap.stop) 280 | break; 281 | let seg = msg[0][idx]; 282 | let addr = seg[0]; 283 | let dat = seg[1]; 284 | let len = dat.length; 285 | document.getElementById('iap_epoch').innerText = `[${idx+1}/${msg[0].length}]`; 286 | 287 | document.getElementById('iap_progress').innerText = `Erasing...`; 288 | if (await flash_erase(addr, len)) { 289 | document.getElementById('iap_progress').innerText = `Erase failed`; 290 | stop_iap(); 291 | return; 292 | } 293 | } 294 | 295 | for (let idx = 0; action != "bl" && idx < msg[0].length; idx++) { 296 | if (csa.iap.stop) 297 | break; 298 | let seg = msg[0][idx]; 299 | let addr = seg[0]; 300 | let dat = seg[1]; 301 | let len = dat.length; 302 | let crc_ori = crc16(dat); 303 | document.getElementById('iap_epoch').innerText = `[${idx+1}/${msg[0].length}]`; 304 | 305 | if (await flash_write(addr, dat)) { 306 | document.getElementById('iap_progress').innerText = `Write failed`; 307 | stop_iap(); 308 | return; 309 | } 310 | 311 | if (check == "crc") { 312 | let crc_back = await flash_read_crc(addr, len); 313 | if (crc_back == null) { 314 | document.getElementById('iap_progress').innerText = 'Read crc failed.'; 315 | stop_iap(); 316 | return; 317 | } else if (crc_back != crc_ori) { 318 | document.getElementById('iap_progress').innerText = `CRC err: ${val2hex(crc_back, 2)} != ${val2hex(crc_ori, 2)}`; 319 | stop_iap(); 320 | return; 321 | } 322 | document.getElementById('iap_progress').innerText = 'Succeeded with crc check.'; 323 | 324 | } else if (check == "read") { 325 | let buf = await flash_read(addr, len); 326 | if (!buf) { 327 | document.getElementById('iap_progress').innerText = 'Read back failed.'; 328 | stop_iap(); 329 | return; 330 | } 331 | let cmp_ret = compare_dat(dat, buf); 332 | if (cmp_ret !== null) { 333 | document.getElementById('iap_progress').innerText = 334 | `Compare err at: ${cmp_ret} (w: ${val2hex(dat[cmp_ret], 1)}, r: ${val2hex(buf[cmp_ret], 1)})`; 335 | stop_iap(); 336 | return; 337 | } 338 | document.getElementById('iap_progress').innerText = 'Succeeded with read back check.'; 339 | } else { 340 | document.getElementById('iap_progress').innerText = 'Succeeded without check.'; 341 | } 342 | } 343 | 344 | if (action == 'bl_full' && !csa.iap.stop) 345 | await do_reboot(2); 346 | 347 | stop_iap(); 348 | }; 349 | 350 | async function init_iap() { 351 | csa.iap = {}; 352 | csa.plugins.push('iap'); 353 | csa.iap.stop = true; 354 | 355 | document.getElementsByTagName('section')[0].insertAdjacentHTML('beforeend', html); 356 | 357 | let iap_cfg = await csa.db.get('tmp', `${csa.arg.name}/iap.cfg`); 358 | let path = document.getElementById('iap_path'); 359 | let check = document.getElementById('iap_check'); 360 | let action = document.getElementById('iap_action'); 361 | 362 | let port = await alloc_port(); 363 | console.log(`init_iap, alloc port: ${port}`); 364 | csa.iap.proxy_sock = new CDWebSocket(csa.ws_ns, port); 365 | 366 | if (iap_cfg) { 367 | path.value = iap_cfg.path; 368 | check.value = iap_cfg.check; 369 | action.value = iap_cfg.action; 370 | } 371 | 372 | path.onchange = check.onchange = action.onchange = async () => { 373 | await csa.db.set('tmp', `${csa.arg.name}/iap.cfg`, { 374 | path: path.value, 375 | check: check.value, 376 | action: action.value 377 | }); 378 | }; 379 | 380 | document.getElementById('iap_start').onclick = do_iap; 381 | document.getElementById('iap_stop').onclick = stop_iap; 382 | } 383 | 384 | 385 | export { init_iap }; 386 | 387 | -------------------------------------------------------------------------------- /html/plugins/reg_rw.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement (MIT License) 3 | * 4 | * Author: Duke Fong 5 | */ 6 | 7 | import { L } from '../utils/lang.js' 8 | import { escape_html, date2num, val2hex, dat2str, str2dat, dat2hex, hex2dat, hex2float, parse_bigint, 9 | read_file, download, readable_size, readable_float, blob2dat } from '../utils/helper.js'; 10 | import { csa } from '../common.js'; 11 | 12 | const R_ADDR = 0; const R_LEN = 1; const R_FMT = 2; 13 | const R_SHOW = 3; const R_ID = 4; const R_DESC = 5; 14 | 15 | function fmt_size(fmt) { 16 | let f = fmt.replace(/\W/g, ''); // remove non-word chars 17 | let len = 0; 18 | for (let i = 0; i < f.length; i++) { 19 | if (!isNaN(f[i+1])) { // e.g. '{H,B2}' 20 | len += Number(f[++i]); 21 | continue; 22 | } 23 | switch (f[i]) { 24 | case 'c': len += 1; break; 25 | case 'b': len += 1; break; 26 | case 'B': len += 1; break; 27 | case 'h': len += 2; break; 28 | case 'H': len += 2; break; 29 | case 'i': len += 4; break; 30 | case 'I': len += 4; break; 31 | case 'q': len += 8; break; 32 | case 'Q': len += 8; break; 33 | case 'f': len += 4; break; 34 | case 'd': len += 8; break; 35 | } 36 | } 37 | return len; 38 | } 39 | 40 | function reg2str(dat, ofs, fmt, show) { 41 | let ret = ''; 42 | let dv = new DataView(dat.buffer); 43 | let f = fmt.replace(/\W/g, ''); // remove non-word chars 44 | for (let i = 0; i < f.length; i++) { 45 | switch (f[i]) { 46 | case 'c': 47 | let c_len = 1; 48 | switch (show) { 49 | case 1: ret = [ret, `${val2hex(dv.getInt8(ofs, true), 2, true)}`].filter(Boolean).join(' '); break; 50 | default: 51 | let c_hdr = dat[ofs]; 52 | if ((c_hdr & 0b11100000) === 0b11000000) 53 | c_len = 2; // 110xxxxx 54 | else if ((c_hdr & 0b11110000) === 0b11100000) 55 | c_len = 3; // 1110xxxx 56 | else if ((c_hdr & 0b11111000) === 0b11110000) 57 | c_len = 4; // 11110xxx 58 | let d = dat.slice(ofs,ofs+c_len); // handle utf8 59 | ret = [ret, `${dat2str(d)}`].filter(Boolean).join(' '); 60 | } 61 | ofs += isNaN(f[i+1]) ? c_len : Number(f[++i]); 62 | break; 63 | case 'b': 64 | switch (show) { 65 | case 1: ret = [ret, `${val2hex(dv.getInt8(ofs, true), 2, true)}`].filter(Boolean).join(' '); break; 66 | default: ret = [ret, `${dv.getInt8(ofs, true)}`].filter(Boolean).join(' '); 67 | } 68 | ofs += isNaN(f[i+1]) ? 1 : Number(f[++i]); 69 | break; 70 | case 'B': 71 | switch (show) { 72 | case 1: ret = [ret, `${val2hex(dv.getUint8(ofs, true), 2, true)}`].filter(Boolean).join(' '); break; 73 | case 2: ret = [ret, `${dat2hex(dat.slice(ofs,ofs+1), ' ')}`].filter(Boolean).join(' '); break; 74 | default: ret = [ret, `${dv.getUint8(ofs, true)}`].filter(Boolean).join(' '); 75 | } 76 | ofs += isNaN(f[i+1]) ? 1 : Number(f[++i]); 77 | break; 78 | case 'h': 79 | switch (show) { 80 | case 1: ret = [ret, `${val2hex(dv.getInt16(ofs, true), 4, true)}`].filter(Boolean).join(' '); break; 81 | default: ret = [ret, `${dv.getInt16(ofs, true)}`].filter(Boolean).join(' '); 82 | } 83 | ofs += isNaN(f[i+1]) ? 2 : Number(f[++i]); 84 | break; 85 | case 'H': 86 | switch (show) { 87 | case 1: ret = [ret, `${val2hex(dv.getUint16(ofs, true), 4, true)}`].filter(Boolean).join(' '); break; 88 | default: ret = [ret, `${dv.getUint16(ofs, true)}`].filter(Boolean).join(' '); 89 | } 90 | ofs += isNaN(f[i+1]) ? 2 : Number(f[++i]); 91 | break; 92 | case 'i': 93 | switch (show) { 94 | case 1: ret = [ret, `${val2hex(dv.getInt32(ofs, true), 8, true)}`].filter(Boolean).join(' '); break; 95 | default: ret = [ret, `${dv.getInt32(ofs, true)}`].filter(Boolean).join(' '); 96 | } 97 | ofs += isNaN(f[i+1]) ? 4 : Number(f[++i]); 98 | break; 99 | case 'I': 100 | switch (show) { 101 | case 1: ret = [ret, `${val2hex(dv.getUint32(ofs, true), 8, true)}`].filter(Boolean).join(' '); break; 102 | default: ret = [ret, `${dv.getUint32(ofs, true)}`].filter(Boolean).join(' '); 103 | } 104 | ofs += isNaN(f[i+1]) ? 4 : Number(f[++i]); 105 | break; 106 | case 'q': 107 | switch (show) { 108 | case 1: ret = [ret, `${val2hex(dv.getBigInt64(ofs, true), 16, true)}`].filter(Boolean).join(' '); break; 109 | default: ret = [ret, `${dv.getBigInt64(ofs, true)}`].filter(Boolean).join(' '); 110 | } 111 | ofs += isNaN(f[i+1]) ? 8 : Number(f[++i]); 112 | break; 113 | case 'Q': 114 | switch (show) { 115 | case 1: ret = [ret, `${val2hex(dv.getBigUint64(ofs, true), 16, true)}`].filter(Boolean).join(' '); break; 116 | default: ret = [ret, `${dv.getBigUint64(ofs, true)}`].filter(Boolean).join(' '); 117 | } 118 | ofs += isNaN(f[i+1]) ? 8 : Number(f[++i]); 119 | break; 120 | case 'f': 121 | switch (show) { 122 | case 1: ret = [ret, `${val2hex(dv.getFloat32(ofs, true), 8, true, false, true)}`].filter(Boolean).join(' '); break; 123 | default: ret = [ret, `${readable_float(dv.getFloat32(ofs, true))}`].filter(Boolean).join(' '); 124 | } 125 | ofs += isNaN(f[i+1]) ? 4 : Number(f[++i]); 126 | break; 127 | case 'd': 128 | switch (show) { 129 | case 1: ret = [ret, `${val2hex(dv.getFloat64(ofs, true), 16, true, false, true)}`].filter(Boolean).join(' '); break; 130 | default: ret = [ret, `${readable_float(dv.getFloat64(ofs, true), true)}`].filter(Boolean).join(' '); 131 | } 132 | ofs += isNaN(f[i+1]) ? 8 : Number(f[++i]); 133 | break; 134 | } 135 | } 136 | return [ret, ofs]; 137 | } 138 | 139 | async function read_reg_val(r_idx, read_dft=false) { 140 | set_input_bg('r', r_idx, '#D5F5E3'); 141 | let addr = csa.reg.reg_r[r_idx][0]; 142 | let len = csa.reg.reg_r[r_idx][1]; 143 | 144 | let dat = new Uint8Array([read_dft ? 0x01 : 0x00, 0, 0, len]); 145 | let dv = new DataView(dat.buffer); 146 | dv.setUint16(1, addr, true); 147 | 148 | csa.reg.proxy_sock_regr.flush(); 149 | await csa.reg.proxy_sock_regr.sendto({'dst': [csa.arg.tgt, 0x5], 'dat': dat}, ['server', 'proxy']); 150 | console.log('read reg wait ret'); 151 | let ret = await csa.reg.proxy_sock_regr.recvfrom(1000); 152 | console.log('read reg ret', ret); 153 | if (ret && (ret[0].dat[0] & 0xf) == 0) { 154 | if (read_dft) 155 | csa.reg.reg_dft_r[r_idx] = true; 156 | 157 | let start = addr; 158 | let found_start = false; 159 | for (let i = 0; i < csa.cfg.reg.list.length; i++) { 160 | let r = csa.cfg.reg.list[i]; 161 | 162 | if (!found_start) { 163 | if (start == r[R_ADDR]) { 164 | found_start = true; 165 | } else { 166 | continue; 167 | } 168 | } 169 | 170 | let ofs = r[R_ADDR] - start; 171 | if (ofs >= len) 172 | break; 173 | 174 | if (r[R_FMT][0] == '{') { 175 | let one_size = fmt_size(r[R_FMT]); 176 | let count = Math.trunc(r[R_LEN] / one_size); 177 | for (let n = 0; n < count; n++) { 178 | let [str, ofs] = reg2str(ret[0].dat.slice(1), r[R_ADDR] - start + one_size * n, r[R_FMT], r[R_SHOW]); 179 | if (read_dft) { 180 | let elem = csa.reg.elm[`reg_dft.${r[R_ID]}.${n}`]; 181 | elem.setAttribute('data-tooltip', `Default: ${str}\nFormat: ${r[R_FMT]}`); 182 | } else { 183 | let elem = csa.reg.elm[`reg.${r[R_ID]}.${n}`]; 184 | elem.value = str; 185 | } 186 | } 187 | } else if (r[R_FMT][0] == '[') { 188 | let one_size = fmt_size(r[R_FMT]); 189 | let count = Math.trunc(r[R_LEN] / one_size); 190 | let val = ''; 191 | let join = r[R_FMT][1] == 'c' && r[R_SHOW] == 0 ? '' : ' '; 192 | for (let n = 0; n < count; /**/) { 193 | let cur_ofs = r[R_ADDR] - start + one_size * n; 194 | let [str, ofs] = reg2str(ret[0].dat.slice(1), cur_ofs, r[R_FMT], r[R_SHOW]); 195 | val = [val, str].filter(Boolean).join(join); 196 | n += Math.trunc((ofs - cur_ofs) / one_size); 197 | } 198 | 199 | if (read_dft) 200 | csa.reg.elm[`reg_dft.${r[R_ID]}`].setAttribute('data-tooltip', `Default: ${val}\nFormat: ${r[R_FMT]}`); 201 | else 202 | csa.reg.elm[`reg.${r[R_ID]}`].value = val; 203 | 204 | } else { 205 | let [str,ofs] = reg2str(ret[0].dat.slice(1), r[R_ADDR] - start, r[R_FMT], r[R_SHOW]); 206 | if (read_dft) { 207 | let elem = csa.reg.elm[`reg_dft.${r[R_ID]}`]; 208 | elem.setAttribute('data-tooltip', `Default: ${str}\nFormat: ${r[R_FMT]}`); 209 | } else { 210 | let elem = csa.reg.elm[`reg.${r[R_ID]}`]; 211 | elem.value = str; 212 | } 213 | } 214 | 215 | } 216 | } else { 217 | console.warn('read reg err'); 218 | set_input_bg('r', r_idx, '#F5B7B180'); 219 | return -1; 220 | } 221 | 222 | if (!read_dft && !csa.reg.reg_dft_r[r_idx]) { 223 | console.log('read default'); 224 | return await read_reg_val(r_idx, true); 225 | } else { 226 | set_input_bg('r', r_idx, '#D5F5E360'); 227 | setTimeout(() => { set_input_bg('r', r_idx, ''); }, 100); 228 | return 0; 229 | } 230 | } 231 | 232 | 233 | function str2reg(dat, ofs, fmt, show, str, s_idx) { 234 | let dv = new DataView(dat.buffer); 235 | let f = fmt.replace(/\W/g, ''); // remove non-word chars 236 | let str_a = str.split(' '); 237 | for (let i = 0; i < f.length; i++) { 238 | switch (f[i]) { 239 | case 'c': 240 | switch (show) { 241 | case 1: dv.setInt8(ofs, parseInt(str_a[s_idx]), true); break; 242 | default: 243 | let str_dat = str2dat(str); // handle utf8 244 | let str_idx = str_dat.slice(s_idx,s_idx+1); 245 | if (!str_idx.length) 246 | str_idx = new Uint8Array(1); // zero 247 | dat.set(str_idx, ofs); 248 | } 249 | ofs += isNaN(f[i+1]) ? 1 : Number(f[++i]); 250 | break; 251 | case 'b': 252 | dv.setInt8(ofs, parseInt(str_a[s_idx]), true); 253 | ofs += isNaN(f[i+1]) ? 1 : Number(f[++i]); 254 | break; 255 | case 'B': 256 | switch (show) { 257 | case 2: dat.set(hex2dat(str_a[s_idx]).slice(0,1), ofs); break; 258 | default: dv.setUint8(ofs, parseInt(str_a[s_idx]), true); 259 | } 260 | ofs += isNaN(f[i+1]) ? 1 : Number(f[++i]); 261 | break; 262 | case 'h': 263 | dv.setInt16(ofs, parseInt(str_a[s_idx]), true); 264 | ofs += isNaN(f[i+1]) ? 2 : Number(f[++i]); 265 | break; 266 | case 'H': 267 | dv.setUint16(ofs, parseInt(str_a[s_idx]), true); 268 | ofs += isNaN(f[i+1]) ? 2 : Number(f[++i]); 269 | break; 270 | case 'i': 271 | dv.setInt32(ofs, parseInt(str_a[s_idx]), true); 272 | ofs += isNaN(f[i+1]) ? 4 : Number(f[++i]); 273 | break; 274 | case 'I': 275 | dv.setUint32(ofs, parseInt(str_a[s_idx]), true); 276 | ofs += isNaN(f[i+1]) ? 4 : Number(f[++i]); 277 | break; 278 | case 'q': 279 | dv.setBigInt64(ofs, parse_bigint(str_a[s_idx]), true); 280 | ofs += isNaN(f[i+1]) ? 8 : Number(f[++i]); 281 | break; 282 | case 'Q': 283 | dv.setBigUint64(ofs, parse_bigint(str_a[s_idx]), true); 284 | ofs += isNaN(f[i+1]) ? 8 : Number(f[++i]); 285 | break; 286 | case 'f': 287 | switch (show) { 288 | case 1: dv.setFloat32(ofs, hex2float(str_a[s_idx]), true); break; 289 | default: dv.setFloat32(ofs, parseFloat(str_a[s_idx]), true); 290 | } 291 | ofs += isNaN(f[i+1]) ? 4 : Number(f[++i]); 292 | break; 293 | case 'd': 294 | switch (show) { 295 | case 1: dv.setFloat64(ofs, hex2float(str_a[s_idx]), true); break; 296 | default: dv.setFloat64(ofs, parseFloat(str_a[s_idx]), true); 297 | } 298 | ofs += isNaN(f[i+1]) ? 8 : Number(f[++i]); 299 | break; 300 | } 301 | s_idx += 1; 302 | } 303 | } 304 | 305 | async function write_reg_val(w_idx) { 306 | set_input_bg('w', w_idx, '#D6EAF8'); 307 | let has_empty = false; 308 | let addr = csa.reg.reg_w[w_idx][0]; 309 | let len = csa.reg.reg_w[w_idx][1]; 310 | 311 | if (!csa.reg.reg_rbw[w_idx]) { // read-before-write 312 | let dat = new Uint8Array([0x00, 0, 0, len]); 313 | let dv = new DataView(dat.buffer); 314 | dv.setUint16(1, addr, true); 315 | 316 | csa.reg.proxy_sock_regw.flush(); 317 | await csa.reg.proxy_sock_regw.sendto({'dst': [csa.arg.tgt, 0x5], 'dat': dat}, ['server', 'proxy']); 318 | console.log('read-before-write wait ret'); 319 | let ret = await csa.reg.proxy_sock_regw.recvfrom(1000); 320 | console.log('read-before-write ret', ret); 321 | if (ret && (ret[0].dat[0] & 0xf) == 0) { 322 | csa.reg.reg_rbw[w_idx] = ret[0].dat.slice(1); 323 | } else { 324 | console.log('read-before-write err'); 325 | set_input_bg('w', w_idx, '#F5B7B180'); 326 | return -1; 327 | } 328 | } 329 | 330 | let dat = new Uint8Array(3 + len); 331 | let dv = new DataView(dat.buffer); 332 | dv.setUint16(1, addr, true); 333 | dat[0] = 0x20; 334 | dat.set(csa.reg.reg_rbw[w_idx], 3); 335 | 336 | console.info('before write reg:', dat2hex(dat, ' ')); 337 | 338 | let start = addr; 339 | let found_start = false; 340 | for (let i = 0; i < csa.cfg.reg.list.length; i++) { 341 | let r = csa.cfg.reg.list[i]; 342 | 343 | if (!found_start) { 344 | if (start == r[R_ADDR]) { 345 | found_start = true; 346 | } else { 347 | continue; 348 | } 349 | } 350 | 351 | let ofs = r[R_ADDR] - start; 352 | if (ofs >= len) 353 | break; 354 | 355 | if (r[R_FMT][0] == '{') { 356 | let one_size = fmt_size(r[R_FMT]); 357 | let count = Math.trunc(r[R_LEN] / one_size); 358 | for (let n = 0; n < count; n++) { 359 | let elem = csa.reg.elm[`reg.${r[R_ID]}.${n}`]; 360 | if (elem.value == '') 361 | has_empty = true; 362 | str2reg(dat, r[R_ADDR]-start+one_size*n+3, r[R_FMT], r[R_SHOW], elem.value, 0); 363 | } 364 | } else if (r[R_FMT][0] == '[') { 365 | let one_size = fmt_size(r[R_FMT]); 366 | let count = Math.trunc(r[R_LEN] / one_size); 367 | let elem = csa.reg.elm[`reg.${r[R_ID]}`]; 368 | if (elem.value == '' && r[R_FMT] != '[c]') 369 | has_empty = true; 370 | for (let n = 0; n < count; n++) 371 | str2reg(dat, r[R_ADDR]-start+one_size*n+3, r[R_FMT], r[R_SHOW], elem.value, n); 372 | 373 | } else { 374 | let elem = csa.reg.elm[`reg.${r[R_ID]}`]; 375 | if (elem.value == '') 376 | has_empty = true; 377 | str2reg(dat, r[R_ADDR]-start+3, r[R_FMT], r[R_SHOW], elem.value, 0); 378 | } 379 | 380 | } 381 | 382 | if (has_empty) { 383 | console.log('write reg: input empty'); 384 | set_input_bg('w', w_idx, '#F5B7B180'); 385 | return -1; 386 | } 387 | 388 | console.info('write reg:', dat2hex(dat, ' ')); 389 | csa.reg.proxy_sock_regw.flush(); 390 | await csa.reg.proxy_sock_regw.sendto({'dst': [csa.arg.tgt, 0x5], 'dat': dat}, ['server', 'proxy']); 391 | console.log('write reg wait ret'); 392 | let ret = await csa.reg.proxy_sock_regw.recvfrom(1000); 393 | console.log('write reg ret', ret); 394 | if (ret && (ret[0].dat[0] & 0xf) == 0) { 395 | console.log('write reg succeeded'); 396 | set_input_bg('w', w_idx, '#D6EAF860'); 397 | setTimeout(() => { set_input_bg('w', w_idx, ''); }, 100); 398 | return 0; 399 | } else { 400 | console.log('write reg err'); 401 | set_input_bg('w', w_idx, '#F5B7B180'); 402 | return -1; 403 | } 404 | } 405 | 406 | 407 | function set_input_bg(rw='r', idx, bg) { 408 | let reg_rw = rw == 'r' ? csa.reg.reg_r : csa.reg.reg_w; 409 | let addr = reg_rw[idx][0]; 410 | let len = reg_rw[idx][1]; 411 | 412 | let start = addr; 413 | let found_start = false; 414 | for (let i = 0; i < csa.cfg.reg.list.length; i++) { 415 | let r = csa.cfg.reg.list[i]; 416 | 417 | if (!found_start) { 418 | if (start == r[R_ADDR]) { 419 | found_start = true; 420 | } else { 421 | continue; 422 | } 423 | } 424 | let ofs = r[R_ADDR] - start; 425 | if (ofs >= len) 426 | break; 427 | 428 | if (r[R_FMT][0] == '{') { 429 | let one_size = fmt_size(r[R_FMT]); 430 | let count = Math.trunc(r[R_LEN] / one_size); 431 | for (let n = 0; n < count; n++) 432 | csa.reg.elm[`reg.${r[R_ID]}.${n}`].style.background = bg; 433 | } else { 434 | csa.reg.elm[`reg.${r[R_ID]}`].style.background = bg; 435 | } 436 | } 437 | } 438 | 439 | export { 440 | fmt_size, reg2str, read_reg_val, str2reg, write_reg_val, 441 | R_ADDR, R_LEN, R_FMT, R_SHOW, R_ID, R_DESC 442 | }; 443 | --------------------------------------------------------------------------------