This commit is contained in:
peli 2020-06-22 06:32:31 -07:00
Родитель 71891a7377
Коммит 197543c810
12 изменённых файлов: 2289 добавлений и 0 удалений

502
package-lock.json сгенерированный Normal file
Просмотреть файл

@ -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="
}
}
}

25
package.json Normal file
Просмотреть файл

@ -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"
}
}

2
src/Makefile Normal file
Просмотреть файл

@ -0,0 +1,2 @@
all:
node node_modules/typescript/bin/tsc

418
src/hf2.ts Normal file
Просмотреть файл

@ -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))
}
}

465
src/jd.ts Normal file
Просмотреть файл

@ -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)
}
}
}
}
}

194
src/jdbl.ts Normal file
Просмотреть файл

@ -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)
}

249
src/jdpretty.ts Normal file
Просмотреть файл

@ -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
}

2
src/jdspy Normal file
Просмотреть файл

@ -0,0 +1,2 @@
#!/usr/bin/env node
require("./built/main")

98
src/main.ts Normal file
Просмотреть файл

@ -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()

16
src/package.json Normal file
Просмотреть файл

@ -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"
}
}

301
src/pxtutils.ts Normal file
Просмотреть файл

@ -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))
}

17
src/tsconfig.json Normal file
Просмотреть файл

@ -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
}
}