зеркало из https://github.com/microsoft/jacdac-ts.git
initial transfer of jdspy
This commit is contained in:
Родитель
71891a7377
Коммит
197543c810
|
@ -0,0 +1,502 @@
|
|||
{
|
||||
"name": "jacdac-ts",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "8.10.61",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.61.tgz",
|
||||
"integrity": "sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q=="
|
||||
},
|
||||
"@types/usb": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/usb/-/usb-1.5.1.tgz",
|
||||
"integrity": "sha512-1qhcYMLJ0I2HcRG3G/nBcRZ0KrrTdGdUNcCkEVgcga4KMlDXWh6LZJjVA6MiWEDa+BOaQTEfGJfuNaQ71IQOpg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/w3c-web-usb": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.4.tgz",
|
||||
"integrity": "sha512-aaOB3EL5WCWBBOYX7W1MKuzspOM9ZJI9s3iziRVypr1N+QyvIgXzCM4lm1iiOQ1VFzZioUPX9bsa23myCbKK4A=="
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
||||
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
||||
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
|
||||
"requires": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^2.0.6"
|
||||
}
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
|
||||
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
|
||||
"requires": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
|
||||
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"decompress-response": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
||||
"requires": {
|
||||
"mimic-response": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
|
||||
},
|
||||
"delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"requires": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||
},
|
||||
"fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
||||
"requires": {
|
||||
"aproba": "^1.0.3",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^1.0.1",
|
||||
"strip-ansi": "^3.0.1",
|
||||
"wide-align": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"mimic-response": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
||||
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.13.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
|
||||
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw=="
|
||||
},
|
||||
"napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
|
||||
},
|
||||
"node-abi": {
|
||||
"version": "2.18.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.18.0.tgz",
|
||||
"integrity": "sha512-yi05ZoiuNNEbyT/xXfSySZE+yVnQW6fxPZuFbLyS1s6b5Kw3HzV2PHOM4XR+nsjzkHxByK+2Wg+yCQbe35l8dw==",
|
||||
"requires": {
|
||||
"semver": "^5.4.1"
|
||||
}
|
||||
},
|
||||
"noop-logger": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
|
||||
"integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI="
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
||||
"requires": {
|
||||
"are-we-there-yet": "~1.1.2",
|
||||
"console-control-strings": "~1.1.0",
|
||||
"gauge": "~2.7.3",
|
||||
"set-blocking": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"prebuild-install": {
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.4.tgz",
|
||||
"integrity": "sha512-AkKN+pf4fSEihjapLEEj8n85YIw/tN6BQqkhzbDc0RvEZGdkpJBGMUYx66AAMcPG2KzmPQS7Cm16an4HVBRRMA==",
|
||||
"requires": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp": "^0.5.1",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^2.7.0",
|
||||
"noop-logger": "^0.1.1",
|
||||
"npmlog": "^4.0.1",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^3.0.3",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"which-pm-runs": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"requires": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"requires": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
|
||||
},
|
||||
"simple-concat": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
|
||||
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
|
||||
},
|
||||
"simple-get": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
|
||||
"integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
|
||||
"requires": {
|
||||
"decompress-response": "^4.2.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
"strip-ansi": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
||||
},
|
||||
"tar-fs": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz",
|
||||
"integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==",
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"tar-stream": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz",
|
||||
"integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==",
|
||||
"requires": {
|
||||
"bl": "^4.0.1",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz",
|
||||
"integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ=="
|
||||
},
|
||||
"usb": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/usb/-/usb-1.6.3.tgz",
|
||||
"integrity": "sha512-23KYMjaWydACd8wgGKMQ4MNwFspAT6Xeim4/9Onqe5Rz/nMb4TM/WHL+qPT0KNFxzNKzAs63n1xQWGEtgaQ2uw==",
|
||||
"requires": {
|
||||
"bindings": "^1.4.0",
|
||||
"nan": "2.13.2",
|
||||
"prebuild-install": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"webusb": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/webusb/-/webusb-2.0.2.tgz",
|
||||
"integrity": "sha512-dzJjGih8rkZ/CAeIQnuOPxbcEW8Vrg39HeiMah3LLntjQgASb8gvzK51RWYd6oDzlPv6d1iJJgFnY1mukSoZew==",
|
||||
"requires": {
|
||||
"@types/node": "^8.0.54",
|
||||
"@types/usb": "^1.5.1",
|
||||
"@types/w3c-web-usb": "^1.0.4",
|
||||
"usb": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"which-pm-runs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
|
||||
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
||||
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
||||
"requires": {
|
||||
"string-width": "^1.0.2 || 2"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "jacdac-ts",
|
||||
"version": "0.0.0",
|
||||
"description": "TypeScript/JavaScript library for JACDAC",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/microsoft/jacdac-ts.git"
|
||||
},
|
||||
"keywords": [
|
||||
"JACDAC"
|
||||
],
|
||||
"author": "Microsoft Corporation",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/microsoft/jacdac-ts/issues"
|
||||
},
|
||||
"homepage": "https://github.com/microsoft/jacdac-ts#readme",
|
||||
"dependencies": {
|
||||
"typescript": "^3.8.3",
|
||||
"webusb": "^2.0.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
all:
|
||||
node node_modules/typescript/bin/tsc
|
|
@ -0,0 +1,418 @@
|
|||
import * as webusb from "webusb"
|
||||
import * as U from "./pxtutils"
|
||||
|
||||
const controlTransferGetReport = 0x01;
|
||||
const controlTransferSetReport = 0x09;
|
||||
const controlTransferOutReport = 0x200;
|
||||
const controlTransferInReport = 0x100;
|
||||
|
||||
// see https://github.com/microsoft/uf2/blob/master/hf2.md for full spec
|
||||
export const HF2_CMD_BININFO = 0x0001 // no arguments
|
||||
export const HF2_MODE_BOOTLOADER = 0x01
|
||||
export const HF2_MODE_USERSPACE = 0x02
|
||||
/*
|
||||
struct HF2_BININFO_Result {
|
||||
uint32_t mode;
|
||||
uint32_t flash_page_size;
|
||||
uint32_t flash_num_pages;
|
||||
uint32_t max_message_size;
|
||||
};
|
||||
*/
|
||||
|
||||
export const HF2_CMD_INFO = 0x0002
|
||||
// no arguments
|
||||
// results is utf8 character array
|
||||
|
||||
export const HF2_CMD_RESET_INTO_APP = 0x0003// no arguments, no result
|
||||
|
||||
export const HF2_CMD_RESET_INTO_BOOTLOADER = 0x0004 // no arguments, no result
|
||||
|
||||
export const HF2_CMD_START_FLASH = 0x0005 // no arguments, no result
|
||||
|
||||
export const HF2_CMD_WRITE_FLASH_PAGE = 0x0006
|
||||
/*
|
||||
struct HF2_WRITE_FLASH_PAGE_Command {
|
||||
uint32_t target_addr;
|
||||
uint32_t data[flash_page_size];
|
||||
};
|
||||
*/
|
||||
// no result
|
||||
|
||||
export const HF2_CMD_CHKSUM_PAGES = 0x0007
|
||||
/*
|
||||
struct HF2_CHKSUM_PAGES_Command {
|
||||
uint32_t target_addr;
|
||||
uint32_t num_pages;
|
||||
};
|
||||
struct HF2_CHKSUM_PAGES_Result {
|
||||
uint16_t chksums[num_pages];
|
||||
};
|
||||
*/
|
||||
|
||||
export const HF2_CMD_READ_WORDS = 0x0008
|
||||
/*
|
||||
struct HF2_READ_WORDS_Command {
|
||||
uint32_t target_addr;
|
||||
uint32_t num_words;
|
||||
};
|
||||
struct HF2_READ_WORDS_Result {
|
||||
uint32_t words[num_words];
|
||||
};
|
||||
*/
|
||||
|
||||
export const HF2_CMD_WRITE_WORDS = 0x0009
|
||||
/*
|
||||
struct HF2_WRITE_WORDS_Command {
|
||||
uint32_t target_addr;
|
||||
uint32_t num_words;
|
||||
uint32_t words[num_words];
|
||||
};
|
||||
*/
|
||||
// no result
|
||||
|
||||
export const HF2_CMD_DMESG = 0x0010
|
||||
// no arguments
|
||||
// results is utf8 character array
|
||||
|
||||
export const HF2_FLAG_SERIAL_OUT = 0x80
|
||||
export const HF2_FLAG_SERIAL_ERR = 0xC0
|
||||
export const HF2_FLAG_CMDPKT_LAST = 0x40
|
||||
export const HF2_FLAG_CMDPKT_BODY = 0x00
|
||||
export const HF2_FLAG_MASK = 0xC0
|
||||
export const HF2_SIZE_MASK = 63
|
||||
|
||||
export const HF2_STATUS_OK = 0x00
|
||||
export const HF2_STATUS_INVALID_CMD = 0x01
|
||||
export const HF2_STATUS_EXEC_ERR = 0x02
|
||||
export const HF2_STATUS_EVENT = 0x80
|
||||
|
||||
// the eventId is overlayed on the tag+status; the mask corresponds
|
||||
// to the HF2_STATUS_EVENT above
|
||||
export const HF2_EV_MASK = 0x800000
|
||||
|
||||
export const HF2_CMD_JDS_CONFIG = 0x0020
|
||||
export const HF2_CMD_JDS_SEND = 0x0021
|
||||
export const HF2_EV_JDS_PACKET = 0x800020
|
||||
|
||||
|
||||
export class Transport {
|
||||
dev: USBDevice;
|
||||
iface: USBInterface;
|
||||
altIface: USBAlternateInterface;
|
||||
epIn: USBEndpoint;
|
||||
epOut: USBEndpoint;
|
||||
readLoopStarted = false;
|
||||
ready = false;
|
||||
|
||||
onData = (v: Uint8Array) => { };
|
||||
onError = (e: Error) => {
|
||||
console.error("HF2 error: " + (e ? e.stack : e))
|
||||
};
|
||||
|
||||
log(msg: string, v?: any) {
|
||||
if (v != undefined)
|
||||
console.log("HF2: " + msg, v)
|
||||
else
|
||||
console.log("HF2: " + msg)
|
||||
}
|
||||
|
||||
|
||||
private clearDev() {
|
||||
if (this.dev) {
|
||||
this.dev = null
|
||||
this.epIn = null
|
||||
this.epOut = null
|
||||
}
|
||||
}
|
||||
|
||||
disconnectAsync() {
|
||||
this.ready = false
|
||||
if (!this.dev) return Promise.resolve()
|
||||
this.log("close device")
|
||||
return this.dev.close()
|
||||
.catch(e => {
|
||||
// just ignore errors closing, most likely device just disconnected
|
||||
})
|
||||
.then(() => {
|
||||
this.clearDev()
|
||||
return U.delay(500)
|
||||
})
|
||||
}
|
||||
|
||||
private recvPacketAsync(): Promise<Uint8Array> {
|
||||
let final = (res: USBInTransferResult) => {
|
||||
if (res.status != "ok")
|
||||
this.error("USB IN transfer failed")
|
||||
let arr = new Uint8Array(res.data.buffer)
|
||||
if (arr.length == 0)
|
||||
return this.recvPacketAsync()
|
||||
return arr
|
||||
}
|
||||
|
||||
if (!this.dev)
|
||||
return Promise.reject(new Error("Disconnected"))
|
||||
|
||||
if (!this.epIn) {
|
||||
return this.dev.controlTransferIn({
|
||||
requestType: "class",
|
||||
recipient: "interface",
|
||||
request: controlTransferGetReport,
|
||||
value: controlTransferInReport,
|
||||
index: this.iface.interfaceNumber
|
||||
}, 64).then(final)
|
||||
}
|
||||
|
||||
return this.dev.transferIn(this.epIn.endpointNumber, 64)
|
||||
.then(final)
|
||||
}
|
||||
|
||||
error(msg: string) {
|
||||
throw new Error(`USB error on device ${this.dev ? this.dev.productName : "n/a"} (${msg})`)
|
||||
}
|
||||
|
||||
private async readLoop() {
|
||||
if (this.readLoopStarted)
|
||||
return
|
||||
this.readLoopStarted = true
|
||||
this.log("start read loop")
|
||||
|
||||
while (true) {
|
||||
if (!this.ready) {
|
||||
break
|
||||
//await U.delay(300)
|
||||
//continue
|
||||
}
|
||||
|
||||
try {
|
||||
const buf = await this.recvPacketAsync()
|
||||
|
||||
if (buf[0]) {
|
||||
// we've got data; retry reading immedietly after processing it
|
||||
this.onData(buf)
|
||||
} else {
|
||||
// throttle down if no data coming
|
||||
await U.delay(5)
|
||||
}
|
||||
} catch (err) {
|
||||
if (this.dev)
|
||||
this.onError(err)
|
||||
await U.delay(300)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendPacketAsync(pkt: Uint8Array) {
|
||||
if (!this.dev)
|
||||
return Promise.reject(new Error("Disconnected"))
|
||||
U.assert(pkt.length <= 64)
|
||||
if (!this.epOut) {
|
||||
return this.dev.controlTransferOut({
|
||||
requestType: "class",
|
||||
recipient: "interface",
|
||||
request: controlTransferSetReport,
|
||||
value: controlTransferOutReport,
|
||||
index: this.iface.interfaceNumber
|
||||
}, pkt).then(res => {
|
||||
if (res.status != "ok")
|
||||
this.error("USB CTRL OUT transfer failed")
|
||||
})
|
||||
}
|
||||
return this.dev.transferOut(this.epOut.endpointNumber, pkt)
|
||||
.then(res => {
|
||||
if (res.status != "ok")
|
||||
this.error("USB OUT transfer failed")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async init() {
|
||||
const usb = new webusb.USB({
|
||||
devicesFound: async devices => {
|
||||
for (const device of devices) {
|
||||
if (device.deviceVersionMajor == 42) {
|
||||
for (const iface of device.configuration.interfaces) {
|
||||
const alt = iface.alternates[0]
|
||||
if (alt.interfaceClass == 0xff && alt.interfaceSubclass == 42) {
|
||||
this.dev = device
|
||||
this.iface = iface
|
||||
this.altIface = alt
|
||||
return device
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
|
||||
this.dev = await usb.requestDevice({ filters: [{}] })
|
||||
this.log("connect device: " + this.dev.manufacturerName + " " + this.dev.productName)
|
||||
|
||||
await this.dev.open()
|
||||
await this.dev.selectConfiguration(1)
|
||||
|
||||
if (this.altIface.endpoints.length) {
|
||||
this.epIn = this.altIface.endpoints.filter(e => e.direction == "in")[0]
|
||||
this.epOut = this.altIface.endpoints.filter(e => e.direction == "out")[0]
|
||||
U.assert(this.epIn.packetSize == 64);
|
||||
U.assert(this.epOut.packetSize == 64);
|
||||
}
|
||||
this.log("claim interface")
|
||||
await this.dev.claimInterface(this.iface.interfaceNumber)
|
||||
this.log("all connected")
|
||||
this.ready = true
|
||||
this.readLoop()
|
||||
}
|
||||
}
|
||||
|
||||
export class Proto {
|
||||
eventHandlers: U.SMap<(buf: Uint8Array) => void> = {}
|
||||
msgs = new U.PromiseBuffer<Uint8Array>()
|
||||
cmdSeq = (Math.random() * 0xffff) | 0;
|
||||
private lock = new U.PromiseQueue();
|
||||
|
||||
constructor(public io: Transport) {
|
||||
let frames: Uint8Array[] = []
|
||||
|
||||
io.onData = buf => {
|
||||
let tp = buf[0] & HF2_FLAG_MASK
|
||||
let len = buf[0] & 63
|
||||
//console.log(`msg tp=${tp} len=${len}`)
|
||||
let frame = new Uint8Array(len)
|
||||
U.memcpy(frame, 0, buf, 1, len)
|
||||
if (tp & HF2_FLAG_SERIAL_OUT) {
|
||||
this.onSerial(frame, tp == HF2_FLAG_SERIAL_ERR)
|
||||
return
|
||||
}
|
||||
frames.push(frame)
|
||||
if (tp == HF2_FLAG_CMDPKT_BODY) {
|
||||
return
|
||||
} else {
|
||||
U.assert(tp == HF2_FLAG_CMDPKT_LAST)
|
||||
let total = 0
|
||||
for (let f of frames) total += f.length
|
||||
let r = new Uint8Array(total)
|
||||
let ptr = 0
|
||||
for (let f of frames) {
|
||||
U.memcpy(r, ptr, f)
|
||||
ptr += f.length
|
||||
}
|
||||
frames = []
|
||||
if (r[2] & HF2_STATUS_EVENT) {
|
||||
// asynchronous event
|
||||
this.handleEvent(r)
|
||||
} else {
|
||||
this.msgs.push(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
error(m: string) {
|
||||
return this.io.error(m)
|
||||
}
|
||||
|
||||
talkAsync(cmd: number, data?: Uint8Array) {
|
||||
let len = 8
|
||||
if (data) len += data.length
|
||||
let pkt = new Uint8Array(len)
|
||||
let seq = ++this.cmdSeq & 0xffff
|
||||
U.write32(pkt, 0, cmd);
|
||||
U.write16(pkt, 4, seq);
|
||||
U.write16(pkt, 6, 0);
|
||||
if (data)
|
||||
U.memcpy(pkt, 8, data, 0, data.length)
|
||||
let numSkipped = 0
|
||||
let handleReturnAsync = (): Promise<Uint8Array> =>
|
||||
this.msgs.shiftAsync(1000) // we wait up to a second
|
||||
.then(res => {
|
||||
if (U.read16(res, 0) != seq) {
|
||||
if (numSkipped < 3) {
|
||||
numSkipped++
|
||||
this.io.log(`message out of sync, (${seq} vs ${U.read16(res, 0)}); will re-try`)
|
||||
return handleReturnAsync()
|
||||
}
|
||||
this.error("out of sync")
|
||||
}
|
||||
let info = ""
|
||||
if (res[3])
|
||||
info = "; info=" + res[3]
|
||||
switch (res[2]) {
|
||||
case HF2_STATUS_OK:
|
||||
return res.slice(4)
|
||||
case HF2_STATUS_INVALID_CMD:
|
||||
this.error("invalid command" + info)
|
||||
break
|
||||
case HF2_STATUS_EXEC_ERR:
|
||||
this.error("execution error" + info)
|
||||
break
|
||||
default:
|
||||
this.error("error " + res[2] + info)
|
||||
break
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
return this.lock.enqueue("talk", () =>
|
||||
this.sendMsgAsync(pkt)
|
||||
.then(handleReturnAsync))
|
||||
}
|
||||
|
||||
|
||||
private sendMsgAsync(buf: Uint8Array, serial: number = 0) {
|
||||
// Util.assert(buf.length <= this.maxMsgSize)
|
||||
let frame = new Uint8Array(64)
|
||||
let loop = (pos: number): Promise<void> => {
|
||||
let len = buf.length - pos
|
||||
if (len <= 0) return Promise.resolve()
|
||||
if (len > 63) {
|
||||
len = 63
|
||||
frame[0] = HF2_FLAG_CMDPKT_BODY;
|
||||
} else {
|
||||
frame[0] = HF2_FLAG_CMDPKT_LAST;
|
||||
}
|
||||
if (serial) frame[0] = serial == 1 ? HF2_FLAG_SERIAL_OUT : HF2_FLAG_SERIAL_ERR;
|
||||
frame[0] |= len;
|
||||
for (let i = 0; i < len; ++i)
|
||||
frame[i + 1] = buf[pos + i]
|
||||
return this.io.sendPacketAsync(frame)
|
||||
.then(() => loop(pos + len))
|
||||
}
|
||||
return loop(0)
|
||||
}
|
||||
|
||||
onEvent(id: number, f: (buf: Uint8Array) => void) {
|
||||
U.assert(!!(id & HF2_EV_MASK))
|
||||
this.eventHandlers[id + ""] = f
|
||||
}
|
||||
|
||||
onJDMessage(f: (buf: Uint8Array) => void) {
|
||||
this.talkAsync(HF2_CMD_JDS_CONFIG, U.encodeU32LE([1]))
|
||||
this.onEvent(HF2_EV_JDS_PACKET, f)
|
||||
}
|
||||
|
||||
sendJDMessageAsync(buf: Uint8Array) {
|
||||
return this.talkAsync(HF2_CMD_JDS_SEND, buf)
|
||||
}
|
||||
|
||||
handleEvent(buf: Uint8Array) {
|
||||
let evid = U.read32(buf, 0)
|
||||
let f = this.eventHandlers[evid + ""]
|
||||
if (f) {
|
||||
f(buf.slice(4))
|
||||
} else {
|
||||
this.io.log("unhandled event: " + evid.toString(16))
|
||||
}
|
||||
}
|
||||
onSerial(data: Uint8Array, iserr: boolean) {
|
||||
console.log("SERIAL:", U.bufferToString(data))
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.io.init()
|
||||
const buf = await this.talkAsync(HF2_CMD_INFO)
|
||||
this.io.log("Connected to: " + U.bufferToString(buf))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,465 @@
|
|||
import * as U from "./pxtutils"
|
||||
|
||||
// Registers 0x001-0x07f - r/w common to all services
|
||||
// Registers 0x080-0x0ff - r/w defined per-service
|
||||
// Registers 0x100-0x17f - r/o common to all services
|
||||
// Registers 0x180-0x1ff - r/o defined per-service
|
||||
// Registers 0x200-0xeff - custom, defined per-service
|
||||
// Registers 0xf00-0xfff - reserved for implementation, should not be on the wire
|
||||
|
||||
// this is either binary (0 or non-zero), or can be gradual (eg. brightness of neopixel)
|
||||
export const REG_INTENSITY = 0x01
|
||||
// the primary value of actuator (eg. servo angle)
|
||||
export const REG_VALUE = 0x02
|
||||
// enable/disable streaming
|
||||
export const REG_IS_STREAMING = 0x03
|
||||
// streaming interval in miliseconds
|
||||
export const REG_STREAMING_INTERVAL = 0x04
|
||||
// for analog sensors
|
||||
export const REG_LOW_THRESHOLD = 0x05
|
||||
export const REG_HIGH_THRESHOLD = 0x06
|
||||
// limit power drawn; in mA
|
||||
export const REG_MAX_POWER = 0x07
|
||||
|
||||
// eg. one number for light sensor, all 3 coordinates for accelerometer
|
||||
export const REG_READING = 0x101
|
||||
|
||||
export const CMD_GET_REG = 0x1000
|
||||
export const CMD_SET_REG = 0x2000
|
||||
|
||||
export const CMD_TOP_MASK = 0xf000
|
||||
export const CMD_REG_MASK = 0x0fff
|
||||
|
||||
|
||||
// Commands 0x000-0x07f - common to all services
|
||||
// Commands 0x080-0xeff - defined per-service
|
||||
// Commands 0xf00-0xfff - reserved for implementation
|
||||
// enumeration data for CTRL, ad-data for other services
|
||||
export const CMD_ADVERTISEMENT_DATA = 0x00
|
||||
// event from sensor or on broadcast service
|
||||
export const CMD_EVENT = 0x01
|
||||
// request to calibrate sensor
|
||||
export const CMD_CALIBRATE = 0x02
|
||||
// request human-readable description of service
|
||||
export const CMD_GET_DESCRIPTION = 0x03
|
||||
|
||||
// Commands specific to control service
|
||||
// do nothing
|
||||
export const CMD_CTRL_NOOP = 0x80
|
||||
// blink led or otherwise draw user's attention
|
||||
export const CMD_CTRL_IDENTIFY = 0x81
|
||||
// reset device
|
||||
export const CMD_CTRL_RESET = 0x82
|
||||
|
||||
export const STREAM_PORT_SHIFT = 7
|
||||
export const STREAM_COUNTER_MASK = 0x001f
|
||||
export const STREAM_CLOSE_MASK = 0x0020
|
||||
export const STREAM_METADATA_MASK = 0x0040
|
||||
|
||||
export const JD_SERIAL_HEADER_SIZE = 16
|
||||
export const JD_SERIAL_MAX_PAYLOAD_SIZE = 236
|
||||
export const JD_SERVICE_NUMBER_MASK = 0x3f
|
||||
export const JD_SERVICE_NUMBER_INV_MASK = 0xc0
|
||||
export const JD_SERVICE_NUMBER_CRC_ACK = 0x3f
|
||||
export const JD_SERVICE_NUMBER_STREAM = 0x3e
|
||||
export const JD_SERVICE_NUMBER_CTRL = 0x00
|
||||
|
||||
// the COMMAND flag signifies that the device_identifier is the recipent
|
||||
// (i.e., it's a command for the peripheral); the bit clear means device_identifier is the source
|
||||
// (i.e., it's a report from peripheral or a broadcast message)
|
||||
export const JD_FRAME_FLAG_COMMAND = 0x01
|
||||
// an ACK should be issued with CRC of this package upon reception
|
||||
export const JD_FRAME_FLAG_ACK_REQUESTED = 0x02
|
||||
// the device_identifier contains target service class number
|
||||
export const JD_FRAME_FLAG_IDENTIFIER_IS_SERVICE_CLASS = 0x04
|
||||
|
||||
function error(msg: string) {
|
||||
throw new Error(msg)
|
||||
}
|
||||
|
||||
function log(msg: string, v?: any) {
|
||||
if (v === undefined)
|
||||
console.log("JD: " + msg)
|
||||
else
|
||||
console.log("JD: " + msg, v)
|
||||
}
|
||||
|
||||
function warn(msg: string, v?: any) {
|
||||
if (v === undefined)
|
||||
console.log("JD-WARN: " + msg)
|
||||
else
|
||||
console.log("JD-WARN: " + msg, v)
|
||||
}
|
||||
|
||||
function idiv(a: number, b: number) { return ((a | 0) / (b | 0)) | 0 }
|
||||
function fnv1(data: Uint8Array) {
|
||||
let h = 0x811c9dc5
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
h = Math.imul(h, 0x1000193) ^ data[i]
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
function hash(buf: Uint8Array, bits: number) {
|
||||
bits |= 0
|
||||
if (bits < 1)
|
||||
return 0
|
||||
const h = fnv1(buf)
|
||||
if (bits >= 32)
|
||||
return h >>> 0
|
||||
else
|
||||
return ((h ^ (h >>> bits)) & ((1 << bits) - 1)) >>> 0
|
||||
}
|
||||
|
||||
// 4 letter ID; 0.04%/0.01%/0.002% collision probability among 20/10/5 devices
|
||||
// 3 letter ID; 1.1%/2.6%/0.05%
|
||||
// 2 letter ID; 25%/6.4%/1.5%
|
||||
export function shortDeviceId(devid: string) {
|
||||
const h = hash(U.fromHex(devid), 30)
|
||||
return String.fromCharCode(0x41 + h % 26) +
|
||||
String.fromCharCode(0x41 + idiv(h, 26) % 26) +
|
||||
String.fromCharCode(0x41 + idiv(h, 26 * 26) % 26) +
|
||||
String.fromCharCode(0x41 + idiv(h, 26 * 26 * 26) % 26)
|
||||
}
|
||||
|
||||
const devices_: Device[] = []
|
||||
export const deviceNames: U.SMap<string> = {}
|
||||
|
||||
let sendPacketFn = (p: Packet) => Promise.resolve(undefined)
|
||||
|
||||
export function setSendPacketFn(f: (p: Packet) => Promise<void>) {
|
||||
sendPacketFn = f
|
||||
}
|
||||
|
||||
export function getDevices() { return devices_.slice() }
|
||||
|
||||
export function getDevice(id: string) {
|
||||
let d = devices_.find(d => d.deviceId == id)
|
||||
if (!d)
|
||||
d = new Device(id)
|
||||
return d
|
||||
}
|
||||
|
||||
export class Device {
|
||||
services: Uint8Array
|
||||
lastSeen: number
|
||||
lastServiceUpdate: number
|
||||
currentReading: Uint8Array
|
||||
private _shortId: string
|
||||
|
||||
constructor(public deviceId: string) {
|
||||
devices_.push(this)
|
||||
}
|
||||
|
||||
get name() {
|
||||
return deviceNames[this.deviceId] || deviceNames[this.shortId]
|
||||
}
|
||||
|
||||
get shortId() {
|
||||
// TODO measure if caching is worth it
|
||||
if (!this._shortId)
|
||||
this._shortId = shortDeviceId(this.deviceId)
|
||||
return this._shortId;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.shortId + (this.name ? ` (${this.name})` : ``)
|
||||
}
|
||||
|
||||
hasService(service_class: number) {
|
||||
for (let i = 4; i < this.services.length; i += 4)
|
||||
if (U.getNumber(this.services, U.NumberFormat.UInt32LE, i) == service_class)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
serviceAt(idx: number) {
|
||||
idx <<= 2
|
||||
if (!this.services || idx + 4 > this.services.length)
|
||||
return undefined
|
||||
return U.read32(this.services, idx)
|
||||
}
|
||||
|
||||
sendCtrlCommand(cmd: number, payload: Buffer = null) {
|
||||
const pkt = !payload ? Packet.onlyHeader(cmd) : Packet.from(cmd, payload)
|
||||
pkt.service_number = JD_SERVICE_NUMBER_CTRL
|
||||
pkt.sendCmdAsync(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Packet {
|
||||
_header: Uint8Array;
|
||||
_data: Uint8Array;
|
||||
timestamp: number
|
||||
dev: Device
|
||||
|
||||
private constructor() { }
|
||||
|
||||
static fromBinary(buf: Uint8Array) {
|
||||
const p = new Packet()
|
||||
p._header = buf.slice(0, JD_SERIAL_HEADER_SIZE)
|
||||
p._data = buf.slice(JD_SERIAL_HEADER_SIZE)
|
||||
return p
|
||||
}
|
||||
|
||||
static from(service_command: number, data: Uint8Array) {
|
||||
const p = new Packet()
|
||||
p._header = new Uint8Array(JD_SERIAL_HEADER_SIZE)
|
||||
p.data = data
|
||||
p.service_command = service_command
|
||||
return p
|
||||
}
|
||||
|
||||
static onlyHeader(service_command: number) {
|
||||
return Packet.from(service_command, new Uint8Array(0))
|
||||
}
|
||||
|
||||
toBuffer() {
|
||||
return U.bufferConcat(this._header, this._data)
|
||||
}
|
||||
|
||||
get device_identifier() {
|
||||
return U.toHex(this._header.slice(4, 4 + 8))
|
||||
}
|
||||
set device_identifier(id: string) {
|
||||
const idb = U.fromHex(id)
|
||||
if (idb.length != 8)
|
||||
error("Invalid id")
|
||||
this._header.set(idb, 4)
|
||||
}
|
||||
|
||||
get frame_flags() { return this._header[3] }
|
||||
|
||||
get multicommand_class() {
|
||||
if (this.frame_flags & JD_FRAME_FLAG_IDENTIFIER_IS_SERVICE_CLASS)
|
||||
return U.read32(this._header, 4)
|
||||
return undefined
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._header[12];
|
||||
}
|
||||
|
||||
get requires_ack(): boolean {
|
||||
return (this.frame_flags & JD_FRAME_FLAG_ACK_REQUESTED) ? true : false;
|
||||
}
|
||||
set requires_ack(ack: boolean) {
|
||||
if (ack != this.requires_ack)
|
||||
this._header[3] ^= JD_FRAME_FLAG_ACK_REQUESTED
|
||||
}
|
||||
|
||||
get service_number(): number {
|
||||
return this._header[13] & JD_SERVICE_NUMBER_MASK;
|
||||
}
|
||||
set service_number(service_number: number) {
|
||||
if (service_number == null)
|
||||
throw "service_number not set"
|
||||
this._header[13] = (this._header[13] & JD_SERVICE_NUMBER_INV_MASK) | service_number;
|
||||
}
|
||||
|
||||
get service_class(): number {
|
||||
if (this.dev)
|
||||
return this.dev.serviceAt(this.service_number)
|
||||
return undefined
|
||||
}
|
||||
|
||||
get crc(): number {
|
||||
return U.read16(this._header, 0)
|
||||
}
|
||||
|
||||
get service_command(): number {
|
||||
return U.read16(this._header, 14)
|
||||
}
|
||||
set service_command(cmd: number) {
|
||||
U.write16(this._header, 14, cmd)
|
||||
}
|
||||
|
||||
get is_reg_set() {
|
||||
return (this.service_command >> 12) == (CMD_SET_REG >> 12)
|
||||
}
|
||||
|
||||
get is_reg_get() {
|
||||
return (this.service_command >> 12) == (CMD_GET_REG >> 12)
|
||||
}
|
||||
|
||||
get data(): Uint8Array {
|
||||
return this._data
|
||||
}
|
||||
|
||||
set data(buf: Uint8Array) {
|
||||
if (buf.length > JD_SERIAL_MAX_PAYLOAD_SIZE)
|
||||
throw "Too big"
|
||||
this._header[12] = buf.length
|
||||
this._data = buf
|
||||
}
|
||||
|
||||
get uintData() {
|
||||
let buf = this._data
|
||||
if (buf.length == 0)
|
||||
return undefined
|
||||
if (buf.length < 4)
|
||||
buf = U.bufferConcat(buf, new Uint8Array(4))
|
||||
return U.read32(buf, 0)
|
||||
}
|
||||
|
||||
get intData() {
|
||||
let fmt: U.NumberFormat
|
||||
switch (this._data.length) {
|
||||
case 0:
|
||||
return undefined
|
||||
case 1:
|
||||
fmt = U.NumberFormat.Int8LE
|
||||
break
|
||||
case 2:
|
||||
case 3:
|
||||
fmt = U.NumberFormat.Int16LE
|
||||
break
|
||||
default:
|
||||
fmt = U.NumberFormat.Int32LE
|
||||
break
|
||||
}
|
||||
return this.getNumber(fmt, 0)
|
||||
}
|
||||
|
||||
compress(stripped: Uint8Array[]) {
|
||||
if (stripped.length == 0)
|
||||
return
|
||||
let sz = -4
|
||||
for (let s of stripped) {
|
||||
sz += s.length
|
||||
}
|
||||
const data = new Uint8Array(sz)
|
||||
this._header.set(stripped[0], 12)
|
||||
data.set(stripped[0].slice(4), 0)
|
||||
sz = stripped[0].length - 4
|
||||
for (let s of stripped.slice(1)) {
|
||||
data.set(s, sz)
|
||||
sz += s.length
|
||||
}
|
||||
this._data = data
|
||||
}
|
||||
|
||||
withFrameStripped() {
|
||||
return U.bufferConcat(this._header.slice(12, 12 + 4), this._data)
|
||||
}
|
||||
|
||||
getNumber(fmt: U.NumberFormat, offset: number) {
|
||||
return U.getNumber(this._data, fmt, offset)
|
||||
}
|
||||
|
||||
get is_command() {
|
||||
return !!(this.frame_flags & JD_FRAME_FLAG_COMMAND)
|
||||
}
|
||||
|
||||
get is_report() {
|
||||
return !this.is_command
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let msg = `${this.device_identifier}/${this.service_number}[${this.frame_flags}]: ${this.service_command} sz=${this.size}`
|
||||
if (this.size < 20) msg += ": " + U.toHex(this.data)
|
||||
else msg += ": " + U.toHex(this.data.slice(0, 20)) + "..."
|
||||
return msg
|
||||
}
|
||||
|
||||
sendCoreAsync() {
|
||||
this._header[2] = this.size + 4
|
||||
U.write16(this._header, 0, crc(U.bufferConcat(this._header.slice(2), this._data)))
|
||||
return sendPacketFn(this)
|
||||
}
|
||||
|
||||
sendReportAsync(dev: Device) {
|
||||
if (!dev)
|
||||
return Promise.resolve()
|
||||
this.device_identifier = dev.deviceId
|
||||
return this.sendCoreAsync()
|
||||
}
|
||||
|
||||
sendCmdAsync(dev: Device) {
|
||||
if (!dev)
|
||||
return Promise.resolve()
|
||||
this.device_identifier = dev.deviceId
|
||||
this._header[3] |= JD_FRAME_FLAG_COMMAND
|
||||
return this.sendCoreAsync()
|
||||
}
|
||||
|
||||
sendAsMultiCommandAsync(service_class: number) {
|
||||
this._header[3] |= JD_FRAME_FLAG_IDENTIFIER_IS_SERVICE_CLASS | JD_FRAME_FLAG_COMMAND
|
||||
U.write32(this._header, 4, service_class)
|
||||
U.write32(this._header, 8, 0)
|
||||
return this.sendCoreAsync()
|
||||
}
|
||||
|
||||
static fromFrame(frame: Uint8Array, timestamp: number) {
|
||||
return frameToPackets(frame, timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
function crc(p: Uint8Array) {
|
||||
let crc = 0xffff;
|
||||
for (let i = 0; i < p.length; ++i) {
|
||||
const data = p[i];
|
||||
let x = (crc >> 8) ^ data;
|
||||
x ^= x >> 4;
|
||||
crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ x;
|
||||
crc &= 0xffff;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
function ALIGN(n: number) { return (n + 3) & ~3 }
|
||||
|
||||
function frameToPackets(frame: Uint8Array, timestamp: number) {
|
||||
const size = frame[2] || 0
|
||||
if (frame.length < size + 12) {
|
||||
warn(`${timestamp}ms: got only ${frame.length} bytes; expecting ${size + 12}`)
|
||||
} else if (size < 4) {
|
||||
warn(`${timestamp}ms: empty packet`)
|
||||
} else {
|
||||
const computed = crc(frame.slice(2, size + 12))
|
||||
const actual = U.read16(frame, 0)
|
||||
if (actual != computed)
|
||||
console.log(`crc mismatch; sz=${size} got:${actual}, exp:${computed}`)
|
||||
|
||||
const res: Packet[] = []
|
||||
if (frame.length != 12 + frame[2])
|
||||
warn(`${timestamp}ms: unexpected packet len: ${frame.length}`)
|
||||
for (let ptr = 12; ptr < 12 + frame[2];) {
|
||||
const psz = frame[ptr] + 4
|
||||
const sz = ALIGN(psz)
|
||||
const pkt = U.bufferConcat(frame.slice(0, 12), frame.slice(ptr, ptr + psz))
|
||||
if (ptr + sz > 12 + frame[2])
|
||||
warn(`${timestamp}ms: invalid frame compression, res len=${res.length}`)
|
||||
const p = Packet.fromBinary(pkt)
|
||||
p.timestamp = timestamp
|
||||
res.push(p)
|
||||
ptr += sz
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
export function process(pkt: Packet) {
|
||||
if (pkt.multicommand_class) {
|
||||
//
|
||||
} else if (pkt.is_command) {
|
||||
pkt.dev = getDevice(pkt.device_identifier)
|
||||
} else {
|
||||
const dev = pkt.dev = getDevice(pkt.device_identifier)
|
||||
dev.lastSeen = pkt.timestamp
|
||||
|
||||
if (pkt.service_number == JD_SERVICE_NUMBER_CTRL) {
|
||||
if (pkt.service_command == CMD_ADVERTISEMENT_DATA) {
|
||||
if (!U.bufferEq(pkt.data, dev.services)) {
|
||||
dev.services = pkt.data
|
||||
dev.lastServiceUpdate = pkt.timestamp
|
||||
// reattach(dev)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
import * as U from "./pxtutils"
|
||||
import * as HF2 from "./hf2"
|
||||
import * as jd from "./jd"
|
||||
import * as jdpretty from "./jdpretty"
|
||||
|
||||
const SERVCE_CLASS_BOOTLOADER = 0x1ffa9948
|
||||
|
||||
const BL_CMD_PAGE_DATA = 0x80
|
||||
const BL_SUBPAGE_SIZE = 208
|
||||
|
||||
interface Program {
|
||||
deviceClass: number;
|
||||
flashStart: number;
|
||||
name: string;
|
||||
program: Uint8Array;
|
||||
}
|
||||
|
||||
async function flashOneProgram(hf2: HF2.Proto, info: Program) {
|
||||
const startTime = Date.now()
|
||||
let targetDevice: jd.Device
|
||||
let currPageAddr = -1
|
||||
let currPageError = -1
|
||||
|
||||
let pageSize = 0
|
||||
let flashSize = 0
|
||||
|
||||
const binProgram = info.program
|
||||
|
||||
function timestamp() {
|
||||
return Date.now() - startTime
|
||||
}
|
||||
|
||||
function log(msg: string) {
|
||||
console.log(`BL [${timestamp()}ms]: ${msg}`)
|
||||
}
|
||||
|
||||
log("flashing: " + info.name)
|
||||
|
||||
hf2.onJDMessage(buf => {
|
||||
for (let p of jd.Packet.fromFrame(buf, timestamp())) {
|
||||
jd.process(p)
|
||||
if (!targetDevice &&
|
||||
p.is_report &&
|
||||
p.service_number == 1 &&
|
||||
p.service_command == jd.CMD_ADVERTISEMENT_DATA) {
|
||||
const d = U.decodeU32LE(p.data)
|
||||
if (d[0] == SERVCE_CLASS_BOOTLOADER) {
|
||||
if (!info.deviceClass || d[3] == info.deviceClass) {
|
||||
pageSize = d[1]
|
||||
flashSize = d[2]
|
||||
targetDevice = p.dev
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (targetDevice && p.dev == targetDevice && p.is_report && p.service_number == 1) {
|
||||
if (p.service_command == BL_CMD_PAGE_DATA) {
|
||||
currPageError = U.read32(p.data, 0)
|
||||
currPageAddr = U.read32(p.data, 4)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const pp = jdpretty.printPkt(p, {
|
||||
skipRepeatedAnnounce: true,
|
||||
skipRepeatedReading: true
|
||||
})
|
||||
if (pp)
|
||||
console.log(pp)
|
||||
}
|
||||
})
|
||||
|
||||
log("resetting all devices")
|
||||
|
||||
const rst = jd.Packet.onlyHeader(jd.CMD_CTRL_RESET)
|
||||
await rst.sendAsMultiCommandAsync(0)
|
||||
|
||||
log("asking for bootloaders")
|
||||
|
||||
const p = jd.Packet.onlyHeader(jd.CMD_ADVERTISEMENT_DATA)
|
||||
while (!targetDevice && timestamp() < 5000) {
|
||||
await p.sendAsMultiCommandAsync(SERVCE_CLASS_BOOTLOADER)
|
||||
await U.delay(100)
|
||||
}
|
||||
|
||||
if (!targetDevice)
|
||||
throw "timeout waiting for devices"
|
||||
|
||||
if (binProgram.length > flashSize)
|
||||
throw "program too big"
|
||||
|
||||
log(`flashing ${targetDevice}; available flash=${flashSize / 1024}kb; page=${pageSize}b`)
|
||||
|
||||
const hdSize = 7 * 4
|
||||
const numSubpage = ((pageSize + BL_SUBPAGE_SIZE - 1) / BL_SUBPAGE_SIZE) | 0
|
||||
|
||||
for (let off = 0; off < binProgram.length; off += pageSize) {
|
||||
log(`flash ${off}/${binProgram.length}`)
|
||||
for (; ;) {
|
||||
let currSubpage = 0
|
||||
for (let suboff = 0; suboff < pageSize; suboff += BL_SUBPAGE_SIZE) {
|
||||
let sz = BL_SUBPAGE_SIZE
|
||||
if (suboff + sz > pageSize)
|
||||
sz = pageSize - suboff
|
||||
const data = new Uint8Array(sz + hdSize)
|
||||
U.write32(data, 0, info.flashStart + off)
|
||||
U.write16(data, 4, suboff)
|
||||
data[6] = currSubpage++
|
||||
data[7] = numSubpage - 1
|
||||
data.set(binProgram.slice(off + suboff, off + suboff + sz), hdSize)
|
||||
const p = jd.Packet.from(BL_CMD_PAGE_DATA, data)
|
||||
p.service_number = 1
|
||||
await p.sendCmdAsync(targetDevice)
|
||||
await U.delay(5)
|
||||
}
|
||||
|
||||
currPageError = -1
|
||||
for (let i = 0; i < 100; ++i) {
|
||||
if (currPageError >= 0)
|
||||
break
|
||||
await U.delay(5)
|
||||
}
|
||||
if (currPageAddr != info.flashStart + off)
|
||||
currPageError = -2
|
||||
|
||||
if (currPageError == 0) {
|
||||
break
|
||||
} else {
|
||||
log(`retry; err=${currPageError}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log("flash done; resetting target")
|
||||
|
||||
const rst2 = jd.Packet.onlyHeader(jd.CMD_CTRL_RESET)
|
||||
await rst2.sendCmdAsync(targetDevice)
|
||||
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
program: Uint8Array;
|
||||
name?: string;
|
||||
ignoreDevClass?: boolean;
|
||||
}
|
||||
|
||||
export async function flash(hf2: HF2.Proto, opts: Options) {
|
||||
let binProgram = opts.program
|
||||
const name = opts.name
|
||||
console.log("flash: " + binProgram.length)
|
||||
while (binProgram.length) {
|
||||
const info: Program = {
|
||||
deviceClass: 0,
|
||||
flashStart: 0x0800_0000,
|
||||
name: name,
|
||||
program: binProgram
|
||||
}
|
||||
|
||||
const hdsize = 16 * 4
|
||||
const header = U.decodeU32LE(binProgram.slice(0, hdsize))
|
||||
let endptr = binProgram.length
|
||||
|
||||
if (header[0] == 0x4b50444a && header[1] == 0x1688f310) {
|
||||
const [_magic0, _magic1, pageSz, flashBase, progLen, devClass] = header
|
||||
info.deviceClass = devClass
|
||||
info.program = binProgram.slice(pageSz, pageSz + progLen)
|
||||
info.flashStart = flashBase
|
||||
for (let i = 0; i < 64; ++i) {
|
||||
if (binProgram[hdsize + i] == 0) {
|
||||
info.name = name + ": " + U.bufferToString(binProgram.slice(hdsize, hdsize + i))
|
||||
break
|
||||
}
|
||||
}
|
||||
endptr = (progLen + pageSz * 2 - 1) & ~(pageSz - 1)
|
||||
} else if ((header[0] & 0xff00_0000) == 0x2000_0000) {
|
||||
info.deviceClass = header[8]
|
||||
} else {
|
||||
throw "not a .bin or .jdpk file"
|
||||
}
|
||||
|
||||
if (info.deviceClass && info.deviceClass >> 28 != 3)
|
||||
throw "device class invalid: " + info.deviceClass.toString(16)
|
||||
|
||||
if (opts.ignoreDevClass)
|
||||
info.deviceClass = 0
|
||||
|
||||
await flashOneProgram(hf2, info)
|
||||
|
||||
binProgram = binProgram.slice(endptr)
|
||||
}
|
||||
|
||||
await U.delay(300)
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
import * as U from "./pxtutils"
|
||||
import * as jd from "./jd"
|
||||
|
||||
const service_classes: U.SMap<number> = {
|
||||
"<disabled>": -1,
|
||||
CTRL: 0,
|
||||
LOGGER: 0x12dc1fca,
|
||||
BATTERY: 0x1d2a2acd,
|
||||
ACCELEROMETER: 0x1f140409,
|
||||
BUTTON: 0x1473a263,
|
||||
TOUCHBUTTON: 0x130cf5be,
|
||||
LIGHT_SENSOR: 0x15e7a0ff,
|
||||
MICROPHONE: 0x1a5c5866,
|
||||
THERMOMETER: 0x1421bac7,
|
||||
SWITCH: 0x14218172,
|
||||
PIXEL: 0x1768fbbf,
|
||||
HAPTIC: 0x116b14a3,
|
||||
LIGHT: 0x126f00e0,
|
||||
KEYBOARD: 0x1ae4812d,
|
||||
MOUSE: 0x14bc97bf,
|
||||
GAMEPAD: 0x100527e8,
|
||||
MUSIC: 0x1b57b1d7,
|
||||
SERVO: 0x12fc9103,
|
||||
CONTROLLER: 0x188ae4b8,
|
||||
LCD: 0x18d5284c,
|
||||
MESSAGE_BUS: 0x115cabf5,
|
||||
COLOR_SENSOR: 0x14d6dda2,
|
||||
LIGHT_SPECTRUM_SENSOR: 0x16fa0c0d,
|
||||
PROXIMITY: 0x14c1791b,
|
||||
TOUCH_BUTTONS: 0x1acb49d5,
|
||||
SERVOS: 0x182988d8,
|
||||
ROTARY_ENCODER: 0x10fa29c9,
|
||||
DNS: 0x117729bd,
|
||||
PWM_LIGHT: 0x1fb57453,
|
||||
BOOTLOADER: 0x1ffa9948,
|
||||
ARCADE_CONTROLS: 0x1deaa06e,
|
||||
POWER: 0x1fa4c95a,
|
||||
SLIDER: 0x1f274746,
|
||||
MOTOR: 0x17004cd8,
|
||||
TCP: 0x1b43b70b,
|
||||
WIFI: 0x18aae1fa,
|
||||
}
|
||||
|
||||
const generic_commands: U.SMap<number> = {
|
||||
CMD_ADVERTISEMENT_DATA: 0x00,
|
||||
CMD_EVENT: 0x01,
|
||||
CMD_CALIBRATE: 0x02,
|
||||
CMD_GET_DESCRIPTION: 0x03,
|
||||
/*
|
||||
CMD_CTRL_NOOP: 0x80,
|
||||
CMD_CTRL_IDENTIFY: 0x81,
|
||||
CMD_CTRL_RESET: 0x82,
|
||||
*/
|
||||
}
|
||||
|
||||
const generic_regs: U.SMap<number> = {
|
||||
REG_INTENSITY: 0x01,
|
||||
REG_VALUE: 0x02,
|
||||
REG_IS_STREAMING: 0x03,
|
||||
REG_STREAMING_INTERVAL: 0x04,
|
||||
REG_LOW_THRESHOLD: 0x05,
|
||||
REG_HIGH_THRESHOLD: 0x06,
|
||||
REG_MAX_POWER: 0x07,
|
||||
REG_READING: 0x101
|
||||
}
|
||||
|
||||
const serv_decoders: U.SMap<(p: jd.Packet) => string> = {
|
||||
LOGGER: (pkt: jd.Packet) => {
|
||||
const pri = priority()
|
||||
if (!pri) return null
|
||||
return `${pri} "${U.bufferToString(pkt.data)}"`
|
||||
|
||||
function priority() {
|
||||
switch (pkt.service_command) {
|
||||
case 0x80: return "dbg"
|
||||
case 0x81: return "log"
|
||||
case 0x82: return "warn"
|
||||
case 0x83: return "err"
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reverseLookup(map: U.SMap<number>, n: number) {
|
||||
for (let k of Object.keys(map)) {
|
||||
if (map[k] == n)
|
||||
return k
|
||||
}
|
||||
return toHex(n)
|
||||
}
|
||||
|
||||
function serviceName(n: number) {
|
||||
if (n == null)
|
||||
return "?"
|
||||
return reverseLookup(service_classes, n)
|
||||
}
|
||||
|
||||
function commandName(n: number) {
|
||||
let pref = ""
|
||||
if ((n & jd.CMD_TOP_MASK) == jd.CMD_SET_REG) pref = "SET["
|
||||
else if ((n & jd.CMD_TOP_MASK) == jd.CMD_GET_REG) pref = "GET["
|
||||
if (pref) {
|
||||
const reg = n & jd.CMD_REG_MASK
|
||||
return pref + reverseLookup(generic_regs, reg) + "]"
|
||||
}
|
||||
return reverseLookup(generic_commands, n)
|
||||
}
|
||||
|
||||
|
||||
function toHex(n: number) {
|
||||
return "0x" + n.toString(16)
|
||||
}
|
||||
|
||||
|
||||
function num2str(n: number) {
|
||||
return n + " (0x" + n.toString(16) + ")"
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
skipRepeatedAnnounce?: boolean;
|
||||
skipRepeatedReading?: boolean;
|
||||
}
|
||||
|
||||
export function printPkt(pkt: jd.Packet, opts: Options = {}) {
|
||||
const frame_flags = pkt._header[3]
|
||||
|
||||
let devname = pkt.dev ? pkt.dev.name || pkt.dev.shortId : pkt.device_identifier
|
||||
|
||||
if (frame_flags & jd.JD_FRAME_FLAG_IDENTIFIER_IS_SERVICE_CLASS)
|
||||
devname = "[mul] " + serviceName(pkt.multicommand_class)
|
||||
|
||||
const serv_id = serviceName(pkt?.dev?.serviceAt(pkt.service_number))
|
||||
let service_name = `${serv_id} (${pkt.service_number})`
|
||||
const cmd = pkt.service_command
|
||||
let cmdname = commandName(cmd)
|
||||
if (pkt.service_number == jd.JD_SERVICE_NUMBER_CRC_ACK) {
|
||||
service_name = "CRC-ACK"
|
||||
cmdname = toHex(cmd)
|
||||
}
|
||||
if (pkt.service_number == jd.JD_SERVICE_NUMBER_STREAM) {
|
||||
service_name = "STREAM"
|
||||
cmdname = `port:${cmd >> jd.STREAM_PORT_SHIFT} cnt:${cmd & jd.STREAM_COUNTER_MASK}`
|
||||
if (cmd & jd.STREAM_METADATA_MASK)
|
||||
cmdname += " meta"
|
||||
if (cmd & jd.STREAM_CLOSE_MASK)
|
||||
cmdname += " close"
|
||||
}
|
||||
|
||||
let pdesc = `${devname}/${service_name}: ${cmdname}; sz=${pkt.size}`
|
||||
|
||||
if (frame_flags & jd.JD_FRAME_FLAG_COMMAND)
|
||||
pdesc = 'to ' + pdesc
|
||||
else
|
||||
pdesc = 'from ' + pdesc
|
||||
if (frame_flags & jd.JD_FRAME_FLAG_ACK_REQUESTED)
|
||||
pdesc = `[ack:${toHex(pkt.crc)}] ` + pdesc
|
||||
|
||||
const d = pkt.data
|
||||
if (pkt.dev && pkt.service_number == 0 && pkt.service_command == jd.CMD_ADVERTISEMENT_DATA) {
|
||||
if (pkt.dev.lastServiceUpdate < pkt.timestamp) {
|
||||
if (opts.skipRepeatedAnnounce)
|
||||
return ""
|
||||
else
|
||||
pdesc = " ====== " + pdesc
|
||||
} else {
|
||||
const services = []
|
||||
for (let i = 0; i < pkt.dev.services.length >> 2; i++) {
|
||||
services.push(serviceName(pkt.dev.serviceAt(i)))
|
||||
}
|
||||
pdesc += "; " + "Announce services: " + services.join(", ")
|
||||
}
|
||||
} else {
|
||||
if (pkt.dev && !pkt.is_command && pkt.service_command == (jd.CMD_GET_REG | jd.REG_READING)) {
|
||||
if (opts.skipRepeatedReading && pkt.dev.currentReading && U.bufferEq(pkt.dev.currentReading, pkt.data))
|
||||
return ""
|
||||
pkt.dev.currentReading = pkt.data
|
||||
}
|
||||
|
||||
const decoder = serv_decoders[serv_id]
|
||||
const decoded = decoder ? decoder(pkt) : null
|
||||
if (decoded) {
|
||||
pdesc += "; " + decoded
|
||||
} else if (pkt.service_command == jd.CMD_EVENT) {
|
||||
pdesc += "; ev=" + num2str(pkt.intData) + " arg=" + (U.read32(pkt.data, 4) | 0)
|
||||
} else if (0 < d.length && d.length <= 4) {
|
||||
let v0 = pkt.uintData, v1 = pkt.intData
|
||||
pdesc += "; " + num2str(v0)
|
||||
if (v0 != v1)
|
||||
pdesc += "; signed: " + num2str(v1)
|
||||
} else if (d.length) {
|
||||
pdesc += "; " + U.toHex(d)
|
||||
}
|
||||
}
|
||||
|
||||
return Math.round(pkt.timestamp) + "ms: " + pdesc
|
||||
}
|
||||
|
||||
export interface ParsedFrame {
|
||||
timestamp: number
|
||||
data: Uint8Array
|
||||
info?: string
|
||||
}
|
||||
|
||||
export function parseLog(logcontents: string) {
|
||||
const res: ParsedFrame[] = []
|
||||
let frameBytes = []
|
||||
let lastTime = 0
|
||||
for (let ln of logcontents.split(/\r?\n/)) {
|
||||
let m = /^JD (\d+) ([0-9a-f]+)/i.exec(ln)
|
||||
if (m) {
|
||||
res.push({
|
||||
timestamp: parseInt(m[1]),
|
||||
data: U.fromHex(m[2])
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
m = /^([\d\.]+),Async Serial,.*(0x[A-F0-9][A-F0-9])/.exec(ln)
|
||||
if (!m)
|
||||
continue
|
||||
const tm = parseFloat(m[1])
|
||||
if (lastTime && tm - lastTime > 0.1) {
|
||||
res.push({
|
||||
timestamp: lastTime * 1000,
|
||||
data: new Uint8Array(frameBytes),
|
||||
info: "timeout"
|
||||
})
|
||||
frameBytes = []
|
||||
lastTime = 0
|
||||
}
|
||||
|
||||
lastTime = tm
|
||||
if (ln.indexOf("framing error") > 0) {
|
||||
if (frameBytes.length > 0)
|
||||
res.push({
|
||||
timestamp: lastTime * 1000,
|
||||
data: new Uint8Array(frameBytes),
|
||||
})
|
||||
frameBytes = []
|
||||
lastTime = 0
|
||||
} else {
|
||||
frameBytes.push(parseInt(m[2]))
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env node
|
||||
require("./built/main")
|
|
@ -0,0 +1,98 @@
|
|||
import * as U from "./pxtutils"
|
||||
import * as HF2 from "./hf2"
|
||||
import * as jd from "./jd"
|
||||
import * as jdpretty from "./jdpretty"
|
||||
import * as jdbl from "./jdbl"
|
||||
import { program as commander } from "commander"
|
||||
import * as fs from "fs"
|
||||
|
||||
const dev_ids = {
|
||||
"119c5abca9fd6070": "JDM3.0-ACC-burned",
|
||||
"ffc91289c5dc5280": "JDM3.0-ACC",
|
||||
"766ccc5755a22eb4": "JDM3.0-LIGHT",
|
||||
"259ab02e98bc2752": "F840-0",
|
||||
"69a9eaeb1a7d2bc0": "F840-1",
|
||||
"08514ae8a1995a00": "KITTEN-0",
|
||||
"XEOM": "DEMO-ACC-L",
|
||||
"OEHM": "DEMO-ACC-M",
|
||||
"MTYV": "DEMO-LIGHT",
|
||||
"ZYQT": "DEMO-MONO",
|
||||
"XMMW": "MB-BLUE",
|
||||
"CJFN": "DEMO-CPB",
|
||||
}
|
||||
|
||||
U.jsonCopyFrom(jd.deviceNames, dev_ids)
|
||||
|
||||
|
||||
interface CmdOptions {
|
||||
parseLog?: string;
|
||||
log?: string;
|
||||
all?: boolean;
|
||||
flash?: string;
|
||||
ignoreDevClass?: boolean;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
commander
|
||||
.version("0.0.0")
|
||||
.option("-p, --parse-log <logfile>", "parse log file from jdspy or Logic")
|
||||
.option("-l, --log <logfile>", "in addition to print, save data to file")
|
||||
.option("-a, --all", "print repeated commands")
|
||||
.option("-f, --flash <file.bin>", "flash binary file")
|
||||
.option("-D, --ignore-dev-class", "ignore device class when flashing")
|
||||
.parse(process.argv)
|
||||
const opts = commander as CmdOptions
|
||||
|
||||
function processFrame(frame: jdpretty.ParsedFrame) {
|
||||
if (frame.info)
|
||||
console.warn("FRM: " + frame.info)
|
||||
for (let p of jd.Packet.fromFrame(frame.data, frame.timestamp)) {
|
||||
if (opts.log)
|
||||
fs.appendFileSync(opts.log, `JD ${frame.timestamp} ${U.toHex(frame.data)}\n`)
|
||||
jd.process(p)
|
||||
const pp = jdpretty.printPkt(p, {
|
||||
skipRepeatedAnnounce: !opts.all,
|
||||
skipRepeatedReading: !opts.all
|
||||
})
|
||||
if (pp)
|
||||
console.log(pp)
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.parseLog) {
|
||||
for (const frame of jdpretty.parseLog(fs.readFileSync(opts.parseLog, "utf8")))
|
||||
processFrame(frame)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const startTime = Date.now()
|
||||
const hf2 = new HF2.Proto(new HF2.Transport())
|
||||
try {
|
||||
await hf2.init()
|
||||
|
||||
jd.setSendPacketFn(p =>
|
||||
hf2.sendJDMessageAsync(p.toBuffer())
|
||||
.then(() => { }, err => console.log(err)))
|
||||
|
||||
if (opts.flash) {
|
||||
await jdbl.flash(hf2, {
|
||||
program: fs.readFileSync(opts.flash),
|
||||
name: opts.flash,
|
||||
ignoreDevClass: opts.ignoreDevClass
|
||||
})
|
||||
await hf2.io.disconnectAsync()
|
||||
return
|
||||
}
|
||||
|
||||
hf2.onJDMessage(buf => {
|
||||
processFrame({ data: buf, timestamp: Date.now() - startTime })
|
||||
})
|
||||
} catch (err) {
|
||||
console.error("ERROR: ", err)
|
||||
await hf2.io.disconnectAsync()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
main()
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "jacdac-ts",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "built/main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^5.0.0",
|
||||
"typescript": "^3.8.3",
|
||||
"webusb": "^2.0.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,301 @@
|
|||
export function delay<T>(millis: number, value?: T): Promise<T> {
|
||||
return new Promise((resolve) => setTimeout(() => resolve(value), millis))
|
||||
}
|
||||
|
||||
export function memcpy(trg: Uint8Array, trgOff: number, src: ArrayLike<number>, srcOff?: number, len?: number) {
|
||||
if (srcOff === void 0)
|
||||
srcOff = 0
|
||||
if (len === void 0)
|
||||
len = src.length - srcOff
|
||||
for (let i = 0; i < len; ++i)
|
||||
trg[trgOff + i] = src[srcOff + i]
|
||||
}
|
||||
|
||||
export function bufferEq(a: Uint8Array, b: ArrayLike<number>) {
|
||||
if (a == b)
|
||||
return true
|
||||
if (!a || !b || a.length != b.length)
|
||||
return false
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
if (a[i] != b[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// this will take lower 8 bits from each character
|
||||
export function stringToUint8Array(input: string) {
|
||||
let len = input.length;
|
||||
let res = new Uint8Array(len)
|
||||
for (let i = 0; i < len; ++i)
|
||||
res[i] = input.charCodeAt(i) & 0xff;
|
||||
return res;
|
||||
}
|
||||
|
||||
export function uint8ArrayToString(input: ArrayLike<number>) {
|
||||
let len = input.length;
|
||||
let res = ""
|
||||
for (let i = 0; i < len; ++i)
|
||||
res += String.fromCharCode(input[i]);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
export function fromUTF8(binstr: string) {
|
||||
if (!binstr) return ""
|
||||
|
||||
// escape function is deprecated
|
||||
let escaped = ""
|
||||
for (let i = 0; i < binstr.length; ++i) {
|
||||
let k = binstr.charCodeAt(i) & 0xff
|
||||
if (k == 37 || k > 0x7f) {
|
||||
escaped += "%" + k.toString(16);
|
||||
} else {
|
||||
escaped += binstr.charAt(i)
|
||||
}
|
||||
}
|
||||
|
||||
// decodeURIComponent does the actual UTF8 decoding
|
||||
return decodeURIComponent(escaped)
|
||||
}
|
||||
|
||||
export function toUTF8(str: string, cesu8?: boolean) {
|
||||
let res = "";
|
||||
if (!str) return res;
|
||||
for (let i = 0; i < str.length; ++i) {
|
||||
let code = str.charCodeAt(i);
|
||||
if (code <= 0x7f) res += str.charAt(i);
|
||||
else if (code <= 0x7ff) {
|
||||
res += String.fromCharCode(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
|
||||
} else {
|
||||
if (!cesu8 && 0xd800 <= code && code <= 0xdbff) {
|
||||
let next = str.charCodeAt(++i);
|
||||
if (!isNaN(next))
|
||||
code = 0x10000 + ((code - 0xd800) << 10) + (next - 0xdc00);
|
||||
}
|
||||
|
||||
if (code <= 0xffff)
|
||||
res += String.fromCharCode(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
|
||||
else
|
||||
res += String.fromCharCode(0xf0 | (code >> 18), 0x80 | ((code >> 12) & 0x3f), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
|
||||
}
|
||||
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
export interface SMap<T> {
|
||||
[index: string]: T;
|
||||
}
|
||||
|
||||
export class PromiseBuffer<T> {
|
||||
private waiting: ((v: (T | Error)) => void)[] = [];
|
||||
private available: (T | Error)[] = [];
|
||||
|
||||
drain() {
|
||||
for (let f of this.waiting) {
|
||||
f(new Error("Promise Buffer Reset"))
|
||||
}
|
||||
this.waiting = []
|
||||
this.available = []
|
||||
}
|
||||
|
||||
|
||||
pushError(v: Error) {
|
||||
this.push(v as any)
|
||||
}
|
||||
|
||||
push(v: T) {
|
||||
let f = this.waiting.shift()
|
||||
if (f) f(v)
|
||||
else this.available.push(v)
|
||||
}
|
||||
|
||||
shiftAsync(timeout = 0) {
|
||||
if (this.available.length > 0) {
|
||||
let v = this.available.shift()
|
||||
if (v instanceof Error)
|
||||
return Promise.reject<T>(v)
|
||||
else
|
||||
return Promise.resolve<T>(v)
|
||||
} else
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
let f = (v: (T | Error)) => {
|
||||
if (v instanceof Error) reject(v)
|
||||
else resolve(v)
|
||||
}
|
||||
this.waiting.push(f)
|
||||
if (timeout > 0) {
|
||||
delay(timeout)
|
||||
.then(() => {
|
||||
let idx = this.waiting.indexOf(f)
|
||||
if (idx >= 0) {
|
||||
this.waiting.splice(idx, 1)
|
||||
reject(new Error("Timeout"))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class PromiseQueue {
|
||||
promises: SMap<(() => Promise<any>)[]> = {};
|
||||
|
||||
enqueue<T>(id: string, f: () => Promise<T>): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
let arr = this.promises[id]
|
||||
if (!arr) {
|
||||
arr = this.promises[id] = []
|
||||
}
|
||||
const cleanup = () => {
|
||||
arr.shift()
|
||||
if (arr.length == 0)
|
||||
delete this.promises[id]
|
||||
else
|
||||
arr[0]()
|
||||
}
|
||||
arr.push(() =>
|
||||
f().then(v => {
|
||||
cleanup()
|
||||
resolve(v)
|
||||
}, err => {
|
||||
cleanup()
|
||||
reject(err)
|
||||
}))
|
||||
if (arr.length == 1)
|
||||
arr[0]()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function toHex(bytes: ArrayLike<number>) {
|
||||
let r = ""
|
||||
for (let i = 0; i < bytes.length; ++i)
|
||||
r += ("0" + bytes[i].toString(16)).slice(-2)
|
||||
return r
|
||||
}
|
||||
|
||||
export function fromHex(hex: string) {
|
||||
let r = new Uint8Array(hex.length >> 1)
|
||||
for (let i = 0; i < hex.length; i += 2)
|
||||
r[i >> 1] = parseInt(hex.slice(i, i + 2), 16)
|
||||
return r
|
||||
}
|
||||
|
||||
export interface MutableArrayLike<T> {
|
||||
readonly length: number;
|
||||
[n: number]: T;
|
||||
}
|
||||
|
||||
export function write32(buf: MutableArrayLike<number>, pos: number, v: number) {
|
||||
buf[pos + 0] = (v >> 0) & 0xff;
|
||||
buf[pos + 1] = (v >> 8) & 0xff;
|
||||
buf[pos + 2] = (v >> 16) & 0xff;
|
||||
buf[pos + 3] = (v >> 24) & 0xff;
|
||||
}
|
||||
|
||||
export function write16(buf: MutableArrayLike<number>, pos: number, v: number) {
|
||||
buf[pos + 0] = (v >> 0) & 0xff;
|
||||
buf[pos + 1] = (v >> 8) & 0xff;
|
||||
}
|
||||
|
||||
export function read32(buf: ArrayLike<number>, pos: number) {
|
||||
return (buf[pos] | (buf[pos + 1] << 8) | (buf[pos + 2] << 16) | (buf[pos + 3] << 24)) >>> 0
|
||||
}
|
||||
|
||||
export function read16(buf: ArrayLike<number>, pos: number) {
|
||||
return buf[pos] | (buf[pos + 1] << 8)
|
||||
}
|
||||
|
||||
export function encodeU32LE(words: number[]) {
|
||||
let r = new Uint8Array(words.length * 4)
|
||||
for (let i = 0; i < words.length; ++i)
|
||||
write32(r, i * 4, words[i])
|
||||
return r
|
||||
}
|
||||
|
||||
export function decodeU32LE(buf: Uint8Array) {
|
||||
let res: number[] = []
|
||||
for (let i = 0; i < buf.length; i += 4)
|
||||
res.push(read32(buf, i))
|
||||
return res
|
||||
}
|
||||
|
||||
export const enum NumberFormat {
|
||||
Int8LE = 1,
|
||||
UInt8LE = 2,
|
||||
Int16LE = 3,
|
||||
UInt16LE = 4,
|
||||
Int32LE = 5,
|
||||
Int8BE = 6,
|
||||
UInt8BE = 7,
|
||||
Int16BE = 8,
|
||||
UInt16BE = 9,
|
||||
Int32BE = 10,
|
||||
UInt32LE = 11,
|
||||
UInt32BE = 12,
|
||||
Float32LE = 13,
|
||||
Float64LE = 14,
|
||||
Float32BE = 15,
|
||||
Float64BE = 16,
|
||||
}
|
||||
|
||||
export function getNumber(buf: ArrayLike<number>, fmt: NumberFormat, offset: number) {
|
||||
switch (fmt) {
|
||||
case NumberFormat.UInt8BE:
|
||||
case NumberFormat.UInt8LE:
|
||||
return buf[offset]
|
||||
case NumberFormat.Int8BE:
|
||||
case NumberFormat.Int8LE:
|
||||
return (buf[offset] << 24) >> 24
|
||||
case NumberFormat.UInt16LE:
|
||||
return read16(buf, offset)
|
||||
case NumberFormat.Int16LE:
|
||||
return (read16(buf, offset) << 16) >> 16
|
||||
case NumberFormat.UInt32LE:
|
||||
return read32(buf, offset)
|
||||
case NumberFormat.Int32LE:
|
||||
return read32(buf, offset) >> 0
|
||||
default:
|
||||
throw new Error("unsupported fmt:" + fmt)
|
||||
}
|
||||
}
|
||||
|
||||
export function bufferToString(buf: Uint8Array) {
|
||||
return fromUTF8(uint8ArrayToString(buf))
|
||||
}
|
||||
|
||||
export function bufferConcat(a: Uint8Array, b: Uint8Array) {
|
||||
const r = new Uint8Array(a.length + b.length)
|
||||
r.set(a, 0)
|
||||
r.set(b, a.length)
|
||||
return r
|
||||
}
|
||||
|
||||
export function jsonCopyFrom<T>(trg: T, src: T) {
|
||||
let v = clone(src)
|
||||
for (let k of Object.keys(src)) {
|
||||
(trg as any)[k] = (v as any)[k]
|
||||
}
|
||||
}
|
||||
export function assert(cond: boolean, msg = "Assertion failed") {
|
||||
if (!cond) {
|
||||
debugger
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
export function flatClone<T extends Object>(obj: T): T {
|
||||
if (obj == null) return null
|
||||
let r: any = {}
|
||||
Object.keys(obj).forEach((k) => { r[k] = (obj as any)[k] })
|
||||
return r;
|
||||
}
|
||||
|
||||
export function clone<T>(v: T): T {
|
||||
if (v == null) return null
|
||||
return JSON.parse(JSON.stringify(v))
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"target": "es6",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"newLine": "LF",
|
||||
"outDir": "built",
|
||||
"rootDir": ".",
|
||||
"incremental": true
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче