feat(device): flash device firmware from Agent (#499)
* add dataModelVersion, usbProtocolVersion, slaveProtocolVersion * read the package.json at appstart * flash firmware * update firmware * fix extra resource path * fix import modules * update lock files * fix imports * terminal window * exclude tmp folder from git repo * ok button * auto scroll in xterm * fix maxTry count calculation * optimize logging * optimize timeout * readSync * Add extra delay * fix async call * fix error message in log * fix ok button disable state * retry * list devices * close device after reenumeration * retry snooze * kboot maxtry 10 * retry 100 * remove deprecated toPayload ngrx helper * flash firmware with custom file * fix tslint
This commit is contained in:
committed by
László Monda
parent
f608791a09
commit
297fd3be79
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ dist
|
||||
*.iml
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
tmp/
|
||||
|
||||
413
package-lock.json
generated
413
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -17,18 +17,23 @@
|
||||
"devDependencies": {
|
||||
"@types/electron-devtools-installer": "2.0.2",
|
||||
"@types/electron-settings": "3.0.0",
|
||||
"@types/fs-extra": "4.0.5",
|
||||
"@types/jasmine": "2.6.0",
|
||||
"@types/node": "8.0.30",
|
||||
"@types/jsonfile": "4.0.1",
|
||||
"@types/node": "8.0.53",
|
||||
"@types/node-hid": "0.5.2",
|
||||
"@types/request": "2.0.8",
|
||||
"@types/usb": "1.1.3",
|
||||
"autoprefixer": "6.5.3",
|
||||
"buffer": "5.0.6",
|
||||
"copy-webpack-plugin": "4.0.1",
|
||||
"core-js": "2.4.1",
|
||||
"cross-env": "5.0.5",
|
||||
"decompress": "4.2.0",
|
||||
"decompress-tarbz2": "^4.1.1",
|
||||
"devtron": "1.4.0",
|
||||
"electron": "1.7.5",
|
||||
"electron-builder": "19.42.2",
|
||||
"electron-builder": "19.45.5",
|
||||
"electron-debug": "1.4.0",
|
||||
"electron-devtools-installer": "2.2.0",
|
||||
"electron-log": "2.2.9",
|
||||
@@ -36,10 +41,13 @@
|
||||
"electron-settings": "3.1.2",
|
||||
"exports-loader": "0.6.3",
|
||||
"file-loader": "0.10.0",
|
||||
"fs-extra": "4.0.2",
|
||||
"jsonfile": "4.0.0",
|
||||
"lerna": "2.0.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"npm-run-all": "4.0.2",
|
||||
"pre-commit": "1.2.2",
|
||||
"request": "2.83.0",
|
||||
"rimraf": "2.6.1",
|
||||
"standard-version": "4.2.0",
|
||||
"stylelint": "7.13.0",
|
||||
@@ -83,5 +91,6 @@
|
||||
"sprites": "node ./scripts/generate-svg-sprites",
|
||||
"release": "node ./scripts/release.js",
|
||||
"clean": "lerna exec rimraf ./node_modules ./dist"
|
||||
}
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
356
packages/uhk-agent/package-lock.json
generated
356
packages/uhk-agent/package-lock.json
generated
@@ -1,11 +1,29 @@
|
||||
{
|
||||
"requires": true,
|
||||
"name": "uhk-agent",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/decompress": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.0.tgz",
|
||||
"integrity": "sha512-f7bLkRy09Z+1kid9ylcWMRlJrEWe4ceqtoGvlFQ/hnxL7WIr+AEy5Bv8SCIRDm8FLqTv4VF+hTUCpSd7rHgtkQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "8.0.33"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "8.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.33.tgz",
|
||||
"integrity": "sha512-vmCdO8Bm1ExT+FWfC9sd9r4jwqM7o97gGy2WBshkkXbf/2nLAJQUrZfIhw27yVOtLUev6kSZc4cav/46KbDd8A=="
|
||||
"integrity": "sha512-vmCdO8Bm1ExT+FWfC9sd9r4jwqM7o97gGy2WBshkkXbf/2nLAJQUrZfIhw27yVOtLUev6kSZc4cav/46KbDd8A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=",
|
||||
"dev": true
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
@@ -127,6 +145,11 @@
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
|
||||
"integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg="
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
|
||||
@@ -136,6 +159,43 @@
|
||||
"tweetnacl": "0.14.5"
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz",
|
||||
"integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=",
|
||||
"requires": {
|
||||
"readable-stream": "2.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
|
||||
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
|
||||
"requires": {
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "1.0.0",
|
||||
"process-nextick-args": "1.0.7",
|
||||
"safe-buffer": "5.1.1",
|
||||
"string_decoder": "1.0.3",
|
||||
"util-deprecate": "1.0.2"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-stream": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
|
||||
@@ -166,6 +226,28 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz",
|
||||
"integrity": "sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=",
|
||||
"requires": {
|
||||
"base64-js": "0.0.8",
|
||||
"ieee754": "1.1.8",
|
||||
"isarray": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
}
|
||||
}
|
||||
},
|
||||
"buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
|
||||
},
|
||||
"builtin-modules": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
|
||||
@@ -273,6 +355,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
|
||||
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
|
||||
"requires": {
|
||||
"graceful-readlink": "1.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -374,6 +464,110 @@
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"decompress": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz",
|
||||
"integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=",
|
||||
"requires": {
|
||||
"decompress-tar": "4.1.1",
|
||||
"decompress-tarbz2": "4.1.1",
|
||||
"decompress-targz": "4.1.1",
|
||||
"decompress-unzip": "4.0.1",
|
||||
"graceful-fs": "4.1.11",
|
||||
"make-dir": "1.1.0",
|
||||
"pify": "2.3.0",
|
||||
"strip-dirs": "2.1.0"
|
||||
}
|
||||
},
|
||||
"decompress-bzip2": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-bzip2/-/decompress-bzip2-4.0.0.tgz",
|
||||
"integrity": "sha1-0SVMlJ4F6vYol1QoawY/3Hz/AT8=",
|
||||
"requires": {
|
||||
"file-type": "4.4.0",
|
||||
"seek-bzip": "1.0.5"
|
||||
}
|
||||
},
|
||||
"decompress-tar": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz",
|
||||
"integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==",
|
||||
"requires": {
|
||||
"file-type": "5.2.0",
|
||||
"is-stream": "1.1.0",
|
||||
"tar-stream": "1.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"file-type": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
|
||||
"integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY="
|
||||
}
|
||||
}
|
||||
},
|
||||
"decompress-tarbz2": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz",
|
||||
"integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==",
|
||||
"requires": {
|
||||
"decompress-tar": "4.1.1",
|
||||
"file-type": "6.2.0",
|
||||
"is-stream": "1.1.0",
|
||||
"seek-bzip": "1.0.5",
|
||||
"unbzip2-stream": "1.2.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"file-type": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz",
|
||||
"integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"decompress-targz": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz",
|
||||
"integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==",
|
||||
"requires": {
|
||||
"decompress-tar": "4.1.1",
|
||||
"file-type": "5.2.0",
|
||||
"is-stream": "1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"file-type": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
|
||||
"integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY="
|
||||
}
|
||||
}
|
||||
},
|
||||
"decompress-unzip": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz",
|
||||
"integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=",
|
||||
"requires": {
|
||||
"file-type": "3.9.0",
|
||||
"get-stream": "2.3.1",
|
||||
"pify": "2.3.0",
|
||||
"yauzl": "2.9.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"file-type": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
|
||||
},
|
||||
"yauzl": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz",
|
||||
"integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=",
|
||||
"requires": {
|
||||
"buffer-crc32": "0.2.13",
|
||||
"fd-slicer": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
|
||||
@@ -586,6 +780,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
|
||||
"integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=",
|
||||
"requires": {
|
||||
"once": "1.4.0"
|
||||
}
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
|
||||
@@ -658,6 +860,11 @@
|
||||
"pend": "1.2.0"
|
||||
}
|
||||
},
|
||||
"file-type": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz",
|
||||
"integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU="
|
||||
},
|
||||
"find-replace": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/find-replace/-/find-replace-1.0.3.tgz",
|
||||
@@ -744,6 +951,15 @@
|
||||
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
|
||||
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
|
||||
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
|
||||
"requires": {
|
||||
"object-assign": "4.1.1",
|
||||
"pinkie-promise": "2.0.1"
|
||||
}
|
||||
},
|
||||
"getpass": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
@@ -770,6 +986,11 @@
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
|
||||
},
|
||||
"graceful-readlink": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
||||
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
|
||||
},
|
||||
"har-schema": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||
@@ -833,6 +1054,11 @@
|
||||
"sshpk": "1.13.1"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
|
||||
"integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q="
|
||||
},
|
||||
"indent-string": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
|
||||
@@ -894,6 +1120,16 @@
|
||||
"number-is-nan": "1.0.1"
|
||||
}
|
||||
},
|
||||
"is-natural-number": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz",
|
||||
"integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg="
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||
},
|
||||
"is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
@@ -1041,6 +1277,21 @@
|
||||
"signal-exit": "3.0.2"
|
||||
}
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz",
|
||||
"integrity": "sha512-0Pkui4wLJ7rxvmfUvs87skoEaxmu0hCUApF8nonzpl7q//FWp9zu8W61Scz4sd/kUiqDxvUhtoam2efDyiBzcA==",
|
||||
"requires": {
|
||||
"pify": "3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"pify": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
||||
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
|
||||
}
|
||||
}
|
||||
},
|
||||
"map-obj": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
|
||||
@@ -1787,10 +2038,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"bundled": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
@@ -1800,6 +2047,10 @@
|
||||
"strip-ansi": "3.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"bundled": true
|
||||
},
|
||||
"stringstream": {
|
||||
"version": "0.0.5",
|
||||
"bundled": true
|
||||
@@ -2258,6 +2509,14 @@
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"seek-bzip": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
|
||||
"integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=",
|
||||
"requires": {
|
||||
"commander": "2.8.1"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||
@@ -2342,11 +2601,6 @@
|
||||
"tweetnacl": "0.14.5"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
@@ -2357,6 +2611,11 @@
|
||||
"strip-ansi": "3.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||
},
|
||||
"stringstream": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
|
||||
@@ -2378,6 +2637,14 @@
|
||||
"is-utf8": "0.2.1"
|
||||
}
|
||||
},
|
||||
"strip-dirs": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz",
|
||||
"integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==",
|
||||
"requires": {
|
||||
"is-natural-number": "4.0.1"
|
||||
}
|
||||
},
|
||||
"strip-indent": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
|
||||
@@ -2425,6 +2692,51 @@
|
||||
"inherits": "2.0.3"
|
||||
}
|
||||
},
|
||||
"tar-stream": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz",
|
||||
"integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==",
|
||||
"requires": {
|
||||
"bl": "1.2.1",
|
||||
"end-of-stream": "1.4.0",
|
||||
"readable-stream": "2.3.3",
|
||||
"xtend": "4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
|
||||
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
|
||||
"requires": {
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "1.0.0",
|
||||
"process-nextick-args": "1.0.7",
|
||||
"safe-buffer": "5.1.1",
|
||||
"string_decoder": "1.0.3",
|
||||
"util-deprecate": "1.0.2"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
|
||||
}
|
||||
}
|
||||
},
|
||||
"test-value": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz",
|
||||
@@ -2439,6 +2751,11 @@
|
||||
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz",
|
||||
"integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8="
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
|
||||
},
|
||||
"through2": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz",
|
||||
@@ -2448,6 +2765,14 @@
|
||||
"xtend": "2.1.2"
|
||||
}
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"requires": {
|
||||
"os-tmpdir": "1.0.2"
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
|
||||
@@ -2485,6 +2810,15 @@
|
||||
"resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
|
||||
"integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0="
|
||||
},
|
||||
"unbzip2-stream": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz",
|
||||
"integrity": "sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==",
|
||||
"requires": {
|
||||
"buffer": "3.6.0",
|
||||
"through": "2.3.8"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"command-line-args": "4.0.7",
|
||||
"decompress": "4.2.0",
|
||||
"decompress-bzip2": "4.0.0",
|
||||
"electron": "1.7.9",
|
||||
"electron-is-dev": "0.1.2",
|
||||
"electron-log": "2.2.9",
|
||||
@@ -23,16 +25,21 @@
|
||||
"electron-updater": "2.15.0",
|
||||
"node-hid": "0.5.4",
|
||||
"sudo-prompt": "7.0.0",
|
||||
"uhk-usb": "1.0.0"
|
||||
"tmp": "0.0.33",
|
||||
"uhk-common": "^1.0.0",
|
||||
"uhk-usb": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/decompress": "4.2.0",
|
||||
"@types/node": "8.0.33",
|
||||
"uhk-common": "^1.0.0"
|
||||
"@types/tmp": "0.0.33"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron ./dist/electron-main.js",
|
||||
"build": "webpack && npm run install:build-deps && npm run build:usb",
|
||||
"build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-blhost",
|
||||
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
|
||||
"install:build-deps": "cd ./dist && npm i"
|
||||
"install:build-deps": "cd ./dist && npm i",
|
||||
"download-firmware": "node ../../scripts/download-firmware.js",
|
||||
"copy-blhost": "node ../../scripts/copy-blhost.js"
|
||||
}
|
||||
}
|
||||
|
||||
1
packages/uhk-agent/src/custom_types/decompress.d.ts
vendored
Normal file
1
packages/uhk-agent/src/custom_types/decompress.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module 'decompress';
|
||||
@@ -8,15 +8,16 @@ import { autoUpdater } from 'electron-updater';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import * as commandLineArgs from 'command-line-args';
|
||||
import { UhkHidDevice } from 'uhk-usb';
|
||||
|
||||
import { UhkHidDevice, UhkOperations } from 'uhk-usb';
|
||||
// import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
|
||||
import { CommandLineArgs } from 'uhk-common';
|
||||
import { CommandLineArgs, LogRegExps } from 'uhk-common';
|
||||
import { DeviceService } from './services/device.service';
|
||||
import { logger } from './services/logger.service';
|
||||
import { AppUpdateService } from './services/app-update.service';
|
||||
import { AppService } from './services/app.service';
|
||||
import { SudoService } from './services/sudo.service';
|
||||
import { UhkBlhost } from '../../uhk-usb/src';
|
||||
import * as isDev from 'electron-is-dev';
|
||||
|
||||
const optionDefinitions = [
|
||||
{name: 'addons', type: Boolean, defaultOption: false}
|
||||
@@ -33,13 +34,41 @@ let win: Electron.BrowserWindow;
|
||||
autoUpdater.logger = logger;
|
||||
|
||||
let deviceService: DeviceService;
|
||||
let uhkBlhost: UhkBlhost;
|
||||
let uhkHidDeviceService: UhkHidDevice;
|
||||
let uhkOperations: UhkOperations;
|
||||
let appUpdateService: AppUpdateService;
|
||||
let appService: AppService;
|
||||
let sudoService: SudoService;
|
||||
|
||||
// https://github.com/megahertz/electron-log/issues/44
|
||||
// console.debug starting with Chromium 58 this method is a no-op on Chromium browsers.
|
||||
if (console.debug) {
|
||||
console.debug = (...args: any[]): void => {
|
||||
if (LogRegExps.writeRegExp.test(args[0])) {
|
||||
console.log(args[0]);
|
||||
} else if (LogRegExps.readRegExp.test(args[0])) {
|
||||
console.log(args[0]);
|
||||
} else if (LogRegExps.errorRegExp.test(args[0])) {
|
||||
console.log(args[0]);
|
||||
} else if (LogRegExps.transferRegExp.test(args[0])) {
|
||||
console.log(args[0]);
|
||||
} else {
|
||||
console.log(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
logger.info('[Electron Main] Create new window.');
|
||||
let packagesDir;
|
||||
if (isDev) {
|
||||
packagesDir = path.join(path.join(process.cwd(), process.argv[1]), '../../../../tmp');
|
||||
} else {
|
||||
packagesDir = path.dirname(app.getAppPath());
|
||||
}
|
||||
|
||||
logger.info(`[Electron Main] packagesDir: ${packagesDir}`);
|
||||
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({
|
||||
@@ -54,7 +83,9 @@ function createWindow() {
|
||||
win.setMenuBarVisibility(false);
|
||||
win.maximize();
|
||||
uhkHidDeviceService = new UhkHidDevice(logger);
|
||||
deviceService = new DeviceService(logger, win, uhkHidDeviceService);
|
||||
uhkBlhost = new UhkBlhost(logger, packagesDir);
|
||||
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
|
||||
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations);
|
||||
appUpdateService = new AppUpdateService(logger, win, app);
|
||||
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
|
||||
sudoService = new SudoService(logger);
|
||||
|
||||
7
packages/uhk-agent/src/models/tmp-firmware.ts
Normal file
7
packages/uhk-agent/src/models/tmp-firmware.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { SynchrounousResult } from 'tmp';
|
||||
|
||||
export interface TmpFirmware {
|
||||
rightFirmwarePath: string;
|
||||
leftFirmwarePath: string;
|
||||
tmpDirectory: SynchrounousResult;
|
||||
}
|
||||
@@ -15,5 +15,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"node-hid": "0.5.7"
|
||||
}
|
||||
},
|
||||
"dataModelVersion": "1.0.0",
|
||||
"usbProtocolVersion": "1.2.0",
|
||||
"slaveProtocolVersion": "2.1.0",
|
||||
"firmwareVersion": "3.0.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ipcMain, BrowserWindow } from 'electron';
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
import { UhkHidDevice } from 'uhk-usb';
|
||||
import { readFile } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { CommandLineArgs, IpcEvents, AppStartInfo, LogService } from 'uhk-common';
|
||||
import { AppStartInfo, CommandLineArgs, IpcEvents, LogService } from 'uhk-common';
|
||||
import { MainServiceBase } from './main-service-base';
|
||||
import { DeviceService } from './device.service';
|
||||
|
||||
@@ -14,17 +16,44 @@ export class AppService extends MainServiceBase {
|
||||
super(logService, win);
|
||||
|
||||
ipcMain.on(IpcEvents.app.getAppStartInfo, this.handleAppStartInfo.bind(this));
|
||||
logService.info('AppService init success');
|
||||
logService.info('[AppService] init success');
|
||||
}
|
||||
|
||||
private handleAppStartInfo(event: Electron.Event) {
|
||||
this.logService.info('getStartInfo');
|
||||
private async handleAppStartInfo(event: Electron.Event) {
|
||||
this.logService.info('[AppService] getAppStartInfo');
|
||||
|
||||
const packageJson = await this.getPackageJson();
|
||||
|
||||
const response: AppStartInfo = {
|
||||
commandLineArgs: this.options,
|
||||
deviceConnected: this.deviceService.isConnected,
|
||||
hasPermission: this.uhkHidDeviceService.hasPermission()
|
||||
hasPermission: this.uhkHidDeviceService.hasPermission(),
|
||||
agentVersionInfo: {
|
||||
version: packageJson.version,
|
||||
dataModelVersion: packageJson.dataModelVersion,
|
||||
usbProtocolVersion: packageJson.usbProtocolVersion,
|
||||
slaveProtocolVersion: packageJson.slaveProtocolVersion,
|
||||
firmwareVersion: packageJson.firmwareVersion
|
||||
}
|
||||
};
|
||||
this.logService.info('getStartInfo response:', response);
|
||||
this.logService.info('[AppService] getAppStartInfo response:', response);
|
||||
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the package.json that delivered with the bundle. Do not use require('package.json')
|
||||
* because the deploy process change the package.json after the build
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
private async getPackageJson(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
readFile(join(__dirname, 'package.json'), {encoding: 'utf-8'}, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(JSON.parse(data));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { IpcEvents, LogService, IpcResponse, ConfigurationReply } from 'uhk-common';
|
||||
import { Constants, EepromTransfer, SystemPropertyIds, UsbCommand } from 'uhk-usb';
|
||||
import { ConfigurationReply, IpcEvents, IpcResponse, LogService } from 'uhk-common';
|
||||
import {
|
||||
Constants,
|
||||
convertBufferToIntArray,
|
||||
EepromTransfer,
|
||||
getTransferBuffers,
|
||||
snooze,
|
||||
SystemPropertyIds,
|
||||
UhkHidDevice,
|
||||
UhkOperations,
|
||||
UsbCommand
|
||||
} from 'uhk-usb';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Device, devices } from 'node-hid';
|
||||
import { UhkHidDevice } from 'uhk-usb';
|
||||
import { emptyDir } from 'fs-extra';
|
||||
|
||||
import 'rxjs/add/observable/interval';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
@@ -12,6 +22,9 @@ import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
|
||||
import { saveTmpFirmware } from '../util/save-extract-firmware';
|
||||
import { TmpFirmware } from '../models/tmp-firmware';
|
||||
|
||||
/**
|
||||
* IpcMain pair of the UHK Communication
|
||||
* Functionality:
|
||||
@@ -21,14 +34,17 @@ import 'rxjs/add/operator/distinctUntilChanged';
|
||||
*/
|
||||
export class DeviceService {
|
||||
private pollTimer$: Subscription;
|
||||
private connected: boolean = false;
|
||||
private connected = false;
|
||||
|
||||
constructor(private logService: LogService,
|
||||
private win: Electron.BrowserWindow,
|
||||
private device: UhkHidDevice) {
|
||||
private device: UhkHidDevice,
|
||||
private operations: UhkOperations) {
|
||||
this.pollUhkDevice();
|
||||
ipcMain.on(IpcEvents.device.saveUserConfiguration, this.saveUserConfiguration.bind(this));
|
||||
ipcMain.on(IpcEvents.device.loadConfigurations, this.loadConfigurations.bind(this));
|
||||
ipcMain.on(IpcEvents.device.updateFirmware, this.updateFirmware.bind(this));
|
||||
ipcMain.on(IpcEvents.device.startConnectionPoller, this.pollUhkDevice.bind(this));
|
||||
logService.debug('[DeviceService] init success');
|
||||
}
|
||||
|
||||
@@ -45,6 +61,8 @@ export class DeviceService {
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public async loadConfigurations(event: Electron.Event): Promise<void> {
|
||||
let response: ConfigurationReply;
|
||||
|
||||
try {
|
||||
await this.device.waitUntilKeyboardBusy();
|
||||
const userConfiguration = await this.loadConfiguration(
|
||||
@@ -57,21 +75,22 @@ export class DeviceService {
|
||||
UsbCommand.ReadHardwareConfig,
|
||||
'hardware configuration');
|
||||
|
||||
const response: ConfigurationReply = {
|
||||
response = {
|
||||
success: true,
|
||||
userConfiguration,
|
||||
hardwareConfiguration
|
||||
};
|
||||
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
|
||||
} catch (error) {
|
||||
const response: ConfigurationReply = {
|
||||
response = {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
|
||||
} finally {
|
||||
this.device.close();
|
||||
}
|
||||
|
||||
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +121,7 @@ export class DeviceService {
|
||||
configSize = readBuffer[3] + (readBuffer[4] << 8);
|
||||
}
|
||||
}
|
||||
response = UhkHidDevice.convertBufferToIntArray(configBuffer);
|
||||
response = convertBufferToIntArray(configBuffer);
|
||||
return Promise.resolve(JSON.stringify(response));
|
||||
} catch (error) {
|
||||
const errMsg = `[DeviceService] ${configName} from eeprom error`;
|
||||
@@ -113,9 +132,40 @@ export class DeviceService {
|
||||
|
||||
public close(): void {
|
||||
this.connected = false;
|
||||
this.pollTimer$.unsubscribe();
|
||||
this.stopPollTimer();
|
||||
this.logService.info('[DeviceService] Device connection checker stopped.');
|
||||
}
|
||||
|
||||
public async updateFirmware(event: Electron.Event, data?: string): Promise<void> {
|
||||
const response = new IpcResponse();
|
||||
|
||||
let firmwarePathData: TmpFirmware;
|
||||
|
||||
try {
|
||||
this.stopPollTimer();
|
||||
|
||||
if (data) {
|
||||
firmwarePathData = await saveTmpFirmware(data);
|
||||
await this.operations.updateRightFirmware(firmwarePathData.rightFirmwarePath);
|
||||
await this.operations.updateLeftModule(firmwarePathData.leftFirmwarePath);
|
||||
}
|
||||
else {
|
||||
await this.operations.updateRightFirmware();
|
||||
await this.operations.updateLeftModule();
|
||||
}
|
||||
|
||||
response.success = true;
|
||||
} catch (error) {
|
||||
const err = {message: error.message, stack: error.stack};
|
||||
this.logService.error('[DeviceService] updateFirmware error', err);
|
||||
|
||||
response.error = err;
|
||||
}
|
||||
|
||||
await emptyDir(firmwarePathData.tmpDirectory.name);
|
||||
|
||||
await snooze(500);
|
||||
event.sender.send(IpcEvents.device.updateFirmwareReply, response);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,6 +175,10 @@ export class DeviceService {
|
||||
* @private
|
||||
*/
|
||||
private pollUhkDevice(): void {
|
||||
if (this.pollTimer$) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pollTimer$ = Observable.interval(1000)
|
||||
.startWith(0)
|
||||
.map(() => {
|
||||
@@ -146,6 +200,7 @@ export class DeviceService {
|
||||
*/
|
||||
private async getConfigSizeFromKeyboard(property: SystemPropertyIds): Promise<number> {
|
||||
const buffer = await this.device.write(new Buffer([UsbCommand.GetProperty, property]));
|
||||
this.device.close();
|
||||
const configSize = buffer[1] + (buffer[2] << 8);
|
||||
this.logService.debug('[DeviceService] User config size:', configSize);
|
||||
return configSize;
|
||||
@@ -182,7 +237,7 @@ export class DeviceService {
|
||||
*/
|
||||
private async sendUserConfigToKeyboard(json: string): Promise<void> {
|
||||
const buffer: Buffer = new Buffer(JSON.parse(json).data);
|
||||
const fragments = UhkHidDevice.getTransferBuffers(UsbCommand.UploadUserConfig, buffer);
|
||||
const fragments = getTransferBuffers(UsbCommand.UploadUserConfig, buffer);
|
||||
for (const fragment of fragments) {
|
||||
await this.device.write(fragment);
|
||||
}
|
||||
@@ -190,4 +245,14 @@ export class DeviceService {
|
||||
const applyBuffer = new Buffer([UsbCommand.ApplyConfig]);
|
||||
await this.device.write(applyBuffer);
|
||||
}
|
||||
|
||||
private stopPollTimer(): void {
|
||||
if (!this.pollTimer$) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pollTimer$.unsubscribe();
|
||||
this.pollTimer$ = null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export class SudoService {
|
||||
|
||||
constructor(private logService: LogService) {
|
||||
if (isDev) {
|
||||
this.rootDir = path.join(path.join(process.cwd(), process.argv[1]), '../../..');
|
||||
this.rootDir = path.join(path.join(process.cwd(), process.argv[1]), '..');
|
||||
} else {
|
||||
this.rootDir = path.dirname(app.getAppPath());
|
||||
}
|
||||
|
||||
37
packages/uhk-agent/src/util/save-extract-firmware.ts
Normal file
37
packages/uhk-agent/src/util/save-extract-firmware.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { dirSync } from 'tmp';
|
||||
import * as decompress from 'decompress';
|
||||
import * as decompressTarbz from 'decompress-tarbz2';
|
||||
|
||||
import { TmpFirmware } from '../models/tmp-firmware';
|
||||
|
||||
export async function saveTmpFirmware(data: string): Promise<TmpFirmware> {
|
||||
const tmpDirectory = dirSync();
|
||||
const zipFilePath = path.join(tmpDirectory.name, 'firmware.bz2');
|
||||
|
||||
await writeDataToFile(data, zipFilePath);
|
||||
await decompress(zipFilePath, tmpDirectory.name, {plugins: [decompressTarbz()]});
|
||||
|
||||
return {
|
||||
tmpDirectory,
|
||||
rightFirmwarePath: path.join(tmpDirectory.name, 'devices/uhk60-right/firmware.hex'),
|
||||
leftFirmwarePath: path.join(tmpDirectory.name, 'modules/uhk60-left.bin')
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function writeDataToFile(data: string, filePath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const array: Array<number> = JSON.parse(data);
|
||||
const buffer = new Buffer(array);
|
||||
|
||||
fs.writeFile(filePath, buffer, err => {
|
||||
if (err) {
|
||||
return reject();
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
import { CommandLineArgs } from './command-line-args';
|
||||
import { VersionInformation } from './version-information';
|
||||
|
||||
export interface AppStartInfo {
|
||||
commandLineArgs: CommandLineArgs;
|
||||
deviceConnected: boolean;
|
||||
hasPermission: boolean;
|
||||
/**
|
||||
* This property contains the version information of the deployed agent components
|
||||
*/
|
||||
agentVersionInfo: VersionInformation;
|
||||
}
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from './notification';
|
||||
export * from './ipc-response';
|
||||
export * from './app-start-info';
|
||||
export * from './configuration-reply';
|
||||
export * from './version-information';
|
||||
|
||||
7
packages/uhk-common/src/models/version-information.ts
Normal file
7
packages/uhk-common/src/models/version-information.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface VersionInformation {
|
||||
version: string;
|
||||
dataModelVersion: string;
|
||||
usbProtocolVersion: string;
|
||||
slaveProtocolVersion: string;
|
||||
firmwareVersion: string;
|
||||
}
|
||||
@@ -24,6 +24,9 @@ class Device {
|
||||
public static readonly saveUserConfigurationReply = 'device-save-user-configuration-reply';
|
||||
public static readonly loadConfigurations = 'device-load-configuration';
|
||||
public static readonly loadConfigurationReply = 'device-load-configuration-reply';
|
||||
public static readonly updateFirmware = 'device-update-firmware';
|
||||
public static readonly updateFirmwareReply = 'device-update-firmware-reply';
|
||||
public static readonly startConnectionPoller = 'device-start-connection-poller';
|
||||
}
|
||||
|
||||
export class IpcEvents {
|
||||
|
||||
16
packages/uhk-usb/package-lock.json
generated
16
packages/uhk-usb/package-lock.json
generated
@@ -294,14 +294,6 @@
|
||||
"xtend": "4.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
@@ -312,6 +304,14 @@
|
||||
"strip-ansi": "3.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
|
||||
@@ -9,13 +9,16 @@ export namespace Constants {
|
||||
*/
|
||||
export enum UsbCommand {
|
||||
GetProperty = 0,
|
||||
Reenumerate = 1,
|
||||
UploadUserConfig = 8,
|
||||
ApplyConfig = 9,
|
||||
LaunchEepromTransfer = 12,
|
||||
ReadHardwareConfig = 13,
|
||||
WriteHardwareConfig = 14,
|
||||
ReadUserConfig = 15,
|
||||
GetKeyboardState = 16
|
||||
GetKeyboardState = 16,
|
||||
JumpToModuleBootloader = 18,
|
||||
SendKbootCommandToModule = 19
|
||||
}
|
||||
|
||||
export enum EepromTransfer {
|
||||
@@ -33,3 +36,42 @@ export enum SystemPropertyIds {
|
||||
HardwareConfigSize = 4,
|
||||
MaxUserConfigSize = 5
|
||||
}
|
||||
|
||||
export enum EnumerationModes {
|
||||
Bootloader = 0,
|
||||
Buspal = 1,
|
||||
NormalKeyboard = 2,
|
||||
CompatibleKeyboard = 3
|
||||
}
|
||||
|
||||
export const enumerationModeIdToProductId = {
|
||||
'0': 0x6120,
|
||||
'1': 0x6121,
|
||||
'2': 0x6122,
|
||||
'3': 0x6123
|
||||
};
|
||||
|
||||
export enum EnumerationNameToProductId {
|
||||
bootloader = 0x6120,
|
||||
buspal = 0x6121,
|
||||
normalKeyboard = 0x6122,
|
||||
compatibleKeyboard = 0x6123
|
||||
}
|
||||
|
||||
export enum ModuleSlotToI2cAddress {
|
||||
leftHalf = '0x10',
|
||||
leftAddon = '0x20',
|
||||
rightAddon = '0x30'
|
||||
}
|
||||
|
||||
export enum ModuleSlotToId {
|
||||
leftHalf = 1,
|
||||
leftAddon = 2,
|
||||
rightAddon = 3
|
||||
}
|
||||
|
||||
export enum KbootCommands {
|
||||
idle = 0,
|
||||
ping = 1,
|
||||
reset = 2
|
||||
}
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
export * from './constants';
|
||||
export * from './uhk-blhost';
|
||||
export * from './uhk-hid-device';
|
||||
export * from './uhk-operations';
|
||||
export * from './util';
|
||||
|
||||
89
packages/uhk-usb/src/uhk-blhost.ts
Normal file
89
packages/uhk-usb/src/uhk-blhost.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import * as path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
import { LogService } from 'uhk-common';
|
||||
import { retry } from './util';
|
||||
|
||||
export class UhkBlhost {
|
||||
private blhostPath: string;
|
||||
|
||||
constructor(private logService: LogService,
|
||||
private rootDir: string) {
|
||||
}
|
||||
|
||||
public async runBlhostCommand(params: Array<string>): Promise<void> {
|
||||
const self = this;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const blhostPath = this.getBlhostPath();
|
||||
self.logService.debug(`[blhost] RUN: ${blhostPath} ${params.join(' ')}`);
|
||||
const childProcess = spawn(`"${blhostPath}"`, params, {shell: true});
|
||||
let finished = false;
|
||||
|
||||
childProcess.stdout.on('data', data => {
|
||||
self.logService.debug(`[blhost] STDOUT: ${data}`);
|
||||
});
|
||||
|
||||
childProcess.stderr.on('data', data => {
|
||||
self.logService.error(`[blhost] STDERR: ${data}`);
|
||||
});
|
||||
|
||||
childProcess.on('close', code => {
|
||||
self.logService.debug(`[blhost] CLOSE_CODE: ${code}`);
|
||||
finish(code);
|
||||
});
|
||||
|
||||
childProcess.on('exit', code => {
|
||||
self.logService.debug(`[blhost] EXIT_CODE: ${code}`);
|
||||
finish(code);
|
||||
});
|
||||
|
||||
childProcess.on('error', err => {
|
||||
self.logService.debug(`[blhost] ERROR: ${err}`);
|
||||
});
|
||||
|
||||
function finish(code) {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
|
||||
self.logService.debug(`[blhost] FINISHED: ${code}`);
|
||||
|
||||
if (code !== null && code !== 0) {
|
||||
return reject(new Error(`blhost error code:${code}`));
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async runBlhostCommandRetry(params: Array<string>, maxTry = 100): Promise<void> {
|
||||
return await retry(async () => await this.runBlhostCommand(params), maxTry, this.logService);
|
||||
}
|
||||
|
||||
private getBlhostPath(): string {
|
||||
if (this.blhostPath) {
|
||||
return this.blhostPath;
|
||||
}
|
||||
|
||||
let blhostPath;
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
blhostPath = 'linux/amd64/blhost';
|
||||
break;
|
||||
case 'darwin':
|
||||
blhostPath = 'mac/blhost';
|
||||
break;
|
||||
case 'win32':
|
||||
blhostPath = 'win/blhost.exe';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Could not find blhost path. Unknown platform:${process.platform}`);
|
||||
}
|
||||
|
||||
this.blhostPath = path.join(this.rootDir, `packages/blhost/${blhostPath}`);
|
||||
|
||||
return this.blhostPath;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,24 @@
|
||||
import { Device, devices, HID } from 'node-hid';
|
||||
import { LogService } from 'uhk-common';
|
||||
|
||||
import { Constants, EepromTransfer, UsbCommand } from './constants';
|
||||
import {
|
||||
Constants,
|
||||
EepromTransfer,
|
||||
enumerationModeIdToProductId,
|
||||
EnumerationModes,
|
||||
KbootCommands,
|
||||
ModuleSlotToI2cAddress,
|
||||
ModuleSlotToId,
|
||||
UsbCommand
|
||||
} from './constants';
|
||||
import { bufferToString, getTransferData, retry, snooze } from './util';
|
||||
|
||||
const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
export const BOOTLOADER_TIMEOUT_MS = 5000;
|
||||
|
||||
/**
|
||||
* HID API wrapper to support unified logging and async write
|
||||
*/
|
||||
export class UhkHidDevice {
|
||||
/**
|
||||
* Convert the Buffer to number[]
|
||||
* @param {Buffer} buffer
|
||||
* @returns {number[]}
|
||||
* @private
|
||||
* @static
|
||||
*/
|
||||
public static convertBufferToIntArray(buffer: Buffer): number[] {
|
||||
return Array.prototype.slice.call(buffer, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the communication package into 64 byte fragments
|
||||
* @param {UsbCommand} usbCommand
|
||||
* @param {Buffer} configBuffer
|
||||
* @returns {Buffer[]}
|
||||
* @private
|
||||
*/
|
||||
public static getTransferBuffers(usbCommand: UsbCommand, configBuffer: Buffer): Buffer[] {
|
||||
const fragments: Buffer[] = [];
|
||||
const MAX_SENDING_PAYLOAD_SIZE = Constants.MAX_PAYLOAD_SIZE - 4;
|
||||
for (let offset = 0; offset < configBuffer.length; offset += MAX_SENDING_PAYLOAD_SIZE) {
|
||||
const length = offset + MAX_SENDING_PAYLOAD_SIZE < configBuffer.length
|
||||
? MAX_SENDING_PAYLOAD_SIZE
|
||||
: configBuffer.length - offset;
|
||||
const header = new Buffer([usbCommand, length, offset & 0xFF, offset >> 8]);
|
||||
fragments.push(Buffer.concat([header, configBuffer.slice(offset, offset + length)]));
|
||||
}
|
||||
|
||||
return fragments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the communication package that will send over USB and
|
||||
* - add usb report code as 1st byte
|
||||
* - https://github.com/node-hid/node-hid/issues/187 issue
|
||||
* @param {Buffer} buffer
|
||||
* @returns {number[]}
|
||||
* @private
|
||||
* @static
|
||||
*/
|
||||
private static getTransferData(buffer: Buffer): number[] {
|
||||
const data = UhkHidDevice.convertBufferToIntArray(buffer);
|
||||
// if data start with 0 need to add additional leading zero because HID API remove it.
|
||||
// https://github.com/node-hid/node-hid/issues/187
|
||||
// if (data.length > 0 && data[0] === 0 && process.platform === 'win32') {
|
||||
// data.unshift(0);
|
||||
// }
|
||||
|
||||
// From HID API documentation:
|
||||
// http://www.signal11.us/oss/hidapi/hidapi/doxygen/html/group__API.html#gad14ea48e440cf5066df87cc6488493af
|
||||
// The first byte of data[] must contain the Report ID.
|
||||
// For devices which only support a single report, this must be set to 0x0.
|
||||
data.unshift(0);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert buffer to space separated hexadecimal string
|
||||
* @param {Buffer} buffer
|
||||
* @returns {string}
|
||||
* @private
|
||||
* @static
|
||||
*/
|
||||
private static bufferToString(buffer: Array<number>): string {
|
||||
let str = '';
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
let hex = buffer[i].toString(16) + ' ';
|
||||
if (hex.length <= 2) {
|
||||
hex = '0' + hex;
|
||||
}
|
||||
str += hex;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal variable that represent the USB UHK device
|
||||
* @private
|
||||
@@ -132,7 +65,7 @@ export class UhkHidDevice {
|
||||
this.logService.error('[UhkHidDevice] Transfer error: ', err);
|
||||
return reject(err);
|
||||
}
|
||||
const logString = UhkHidDevice.bufferToString(receivedData);
|
||||
const logString = bufferToString(receivedData);
|
||||
this.logService.debug('[UhkHidDevice] USB[R]:', logString);
|
||||
|
||||
if (receivedData[0] !== 0) {
|
||||
@@ -142,8 +75,8 @@ export class UhkHidDevice {
|
||||
return resolve(Buffer.from(receivedData));
|
||||
});
|
||||
|
||||
const sendData = UhkHidDevice.getTransferData(buffer);
|
||||
this.logService.debug('[UhkHidDevice] USB[W]:', UhkHidDevice.bufferToString(sendData).substr(3));
|
||||
const sendData = getTransferData(buffer);
|
||||
this.logService.debug('[UhkHidDevice] USB[W]:', bufferToString(sendData).substr(3));
|
||||
device.write(sendData);
|
||||
});
|
||||
}
|
||||
@@ -157,13 +90,13 @@ export class UhkHidDevice {
|
||||
* Close the communication chanel with UHK Device
|
||||
*/
|
||||
public close(): void {
|
||||
this.logService.info('[UhkHidDevice] Device communication closing.');
|
||||
this.logService.debug('[UhkHidDevice] Device communication closing.');
|
||||
if (!this._device) {
|
||||
return;
|
||||
}
|
||||
this._device.close();
|
||||
this._device = null;
|
||||
this.logService.info('[UhkHidDevice] Device communication closed.');
|
||||
this.logService.debug('[UhkHidDevice] Device communication closed.');
|
||||
}
|
||||
|
||||
public async waitUntilKeyboardBusy(): Promise<void> {
|
||||
@@ -177,6 +110,77 @@ export class UhkHidDevice {
|
||||
}
|
||||
}
|
||||
|
||||
async reenumerate(enumerationMode: EnumerationModes): Promise<void> {
|
||||
const reenumMode = EnumerationModes[enumerationMode].toString();
|
||||
this.logService.debug(`[UhkHidDevice] Start reenumeration, mode: ${reenumMode}`);
|
||||
|
||||
const message = new Buffer([
|
||||
UsbCommand.Reenumerate,
|
||||
enumerationMode,
|
||||
BOOTLOADER_TIMEOUT_MS & 0xff,
|
||||
(BOOTLOADER_TIMEOUT_MS & 0xff << 8) >> 8,
|
||||
(BOOTLOADER_TIMEOUT_MS & 0xff << 16) >> 16,
|
||||
(BOOTLOADER_TIMEOUT_MS & 0xff << 24) >> 24
|
||||
]);
|
||||
|
||||
const enumeratedProductId = enumerationModeIdToProductId[enumerationMode.toString()];
|
||||
const startTime = new Date();
|
||||
let jumped = false;
|
||||
|
||||
while (new Date().getTime() - startTime.getTime() < 20000) {
|
||||
const devs = devices();
|
||||
this.logService.silly('[UhkHidDevice] reenumeration devices', devs);
|
||||
|
||||
const inBootloaderMode = devs.some((x: Device) =>
|
||||
x.vendorId === Constants.VENDOR_ID &&
|
||||
x.productId === enumeratedProductId);
|
||||
|
||||
if (inBootloaderMode) {
|
||||
this.logService.debug(`[UhkHidDevice] reenumeration devices up`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.silly(`[UhkHidDevice] Could not find reenumerated device: ${reenumMode}. Waiting...`);
|
||||
await snooze(100);
|
||||
|
||||
if (!jumped) {
|
||||
const device = this.getDevice();
|
||||
if (device) {
|
||||
const data = getTransferData(message);
|
||||
this.logService.debug(`[UhkHidDevice] USB[T]: Enumerate device. Mode: ${reenumMode}`);
|
||||
this.logService.debug('[UhkHidDevice] USB[W]:', bufferToString(data).substr(3));
|
||||
device.write(data);
|
||||
device.close();
|
||||
jumped = true;
|
||||
} else {
|
||||
this.logService.silly(`[UhkHidDevice] USB[T]: Enumerate device is not ready yet}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logService.error(`[UhkHidDevice] Could not find reenumerated device: ${reenumMode}. Timeout`);
|
||||
|
||||
throw new Error(`Could not reenumerate as ${reenumMode}`);
|
||||
}
|
||||
|
||||
async sendKbootCommandToModule(module: ModuleSlotToI2cAddress, command: KbootCommands, maxTry = 1): Promise<any> {
|
||||
let transfer;
|
||||
const moduleName = kbootKommandName(module);
|
||||
this.logService.debug(`[UhkHidDevice] USB[T]: Send KbootCommand ${moduleName} ${KbootCommands[command].toString()}`);
|
||||
if (command === KbootCommands.idle) {
|
||||
transfer = new Buffer([UsbCommand.SendKbootCommandToModule, command]);
|
||||
} else {
|
||||
transfer = new Buffer([UsbCommand.SendKbootCommandToModule, command, Number.parseInt(module)]);
|
||||
}
|
||||
await retry(async () => await this.write(transfer), maxTry, this.logService);
|
||||
}
|
||||
|
||||
async jumpToBootloaderModule(module: ModuleSlotToId): Promise<any> {
|
||||
this.logService.debug(`[UhkHidDevice] USB[T]: Jump to bootloader. Module: ${ModuleSlotToId[module].toString()}`);
|
||||
const transfer = new Buffer([UsbCommand.JumpToModuleBootloader, module]);
|
||||
await this.write(transfer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the stored version of HID device. If not exist try to initialize.
|
||||
* @returns {HID}
|
||||
@@ -205,11 +209,11 @@ export class UhkHidDevice {
|
||||
((x.usagePage === 128 && x.usage === 129) || x.interface === 0));
|
||||
|
||||
if (!dev) {
|
||||
this.logService.info('[UhkHidDevice] UHK Device not found:');
|
||||
this.logService.debug('[UhkHidDevice] UHK Device not found:');
|
||||
return null;
|
||||
}
|
||||
const device = new HID(dev.path);
|
||||
this.logService.info('[UhkHidDevice] Used device:', dev);
|
||||
this.logService.debug('[UhkHidDevice] Used device:', dev);
|
||||
return device;
|
||||
}
|
||||
catch (err) {
|
||||
@@ -218,5 +222,20 @@ export class UhkHidDevice {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function kbootKommandName(module: ModuleSlotToI2cAddress): string {
|
||||
switch (module) {
|
||||
case ModuleSlotToI2cAddress.leftHalf:
|
||||
return 'leftHalf';
|
||||
|
||||
case ModuleSlotToI2cAddress.leftAddon:
|
||||
return 'leftAddon';
|
||||
|
||||
case ModuleSlotToI2cAddress.rightAddon:
|
||||
return 'rightAddon';
|
||||
|
||||
default :
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
80
packages/uhk-usb/src/uhk-operations.ts
Normal file
80
packages/uhk-usb/src/uhk-operations.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { LogService } from 'uhk-common';
|
||||
import { EnumerationModes, EnumerationNameToProductId, KbootCommands, ModuleSlotToI2cAddress, ModuleSlotToId } from './constants';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { UhkBlhost } from './uhk-blhost';
|
||||
import { UhkHidDevice } from './uhk-hid-device';
|
||||
import { snooze } from './util';
|
||||
|
||||
export class UhkOperations {
|
||||
constructor(private logService: LogService,
|
||||
private blhost: UhkBlhost,
|
||||
private device: UhkHidDevice,
|
||||
private rootDir: string) {
|
||||
}
|
||||
|
||||
public async updateRightFirmware(firmwarePath = this.getFirmwarePath()) {
|
||||
this.logService.debug('[UhkOperations] Start flashing right firmware');
|
||||
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.bootloader.toString(16)}`];
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.Bootloader);
|
||||
this.device.close();
|
||||
await this.blhost.runBlhostCommand([...prefix, 'flash-security-disable', '0403020108070605']);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'flash-erase-region', '0xc000', '475136']);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'flash-image', `"${firmwarePath}"`]);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'reset']);
|
||||
this.logService.debug('[UhkOperations] End flashing right firmware');
|
||||
}
|
||||
|
||||
public async updateLeftModule(firmwarePath = this.getLeftModuleFirmwarePath()) {
|
||||
this.logService.debug('[UhkOperations] Start flashing left module firmware');
|
||||
|
||||
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.buspal.toString(16)}`];
|
||||
const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf},100k`];
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.NormalKeyboard);
|
||||
this.device.close();
|
||||
await snooze(1000);
|
||||
await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.ping, 100);
|
||||
await snooze(1000);
|
||||
await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf);
|
||||
this.device.close();
|
||||
await this.device.reenumerate(EnumerationModes.Buspal);
|
||||
this.device.close();
|
||||
await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']);
|
||||
await this.blhost.runBlhostCommand([...buspalPrefix, 'flash-erase-all-unsecure']);
|
||||
await this.blhost.runBlhostCommand([...buspalPrefix, 'write-memory', '0x0', `"${firmwarePath}"`]);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'reset']);
|
||||
await snooze(1000);
|
||||
await this.device.reenumerate(EnumerationModes.NormalKeyboard);
|
||||
this.device.close();
|
||||
await snooze(1000);
|
||||
await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.reset, 100);
|
||||
this.device.close();
|
||||
await snooze(1000);
|
||||
await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.idle);
|
||||
this.device.close();
|
||||
|
||||
this.logService.debug('[UhkOperations] End flashing left module firmware');
|
||||
}
|
||||
|
||||
private getFirmwarePath(): string {
|
||||
const firmware = path.join(this.rootDir, 'packages/firmware/devices/uhk60-right/firmware.hex');
|
||||
|
||||
if (fs.existsSync(firmware)) {
|
||||
return firmware;
|
||||
}
|
||||
|
||||
throw new Error(`Could not found firmware ${firmware}`);
|
||||
}
|
||||
|
||||
private getLeftModuleFirmwarePath(): string {
|
||||
const firmware = path.join(this.rootDir, 'packages/firmware/modules/uhk60-left.bin');
|
||||
|
||||
if (fs.existsSync(firmware)) {
|
||||
return firmware;
|
||||
}
|
||||
|
||||
throw new Error(`Could not found firmware ${firmware}`);
|
||||
}
|
||||
}
|
||||
97
packages/uhk-usb/src/util.ts
Normal file
97
packages/uhk-usb/src/util.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Constants, UsbCommand } from './constants';
|
||||
import { LogService } from '../../uhk-common';
|
||||
|
||||
export const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
/**
|
||||
* Convert the Buffer to number[]
|
||||
* @param {Buffer} buffer
|
||||
* @returns {number[]}
|
||||
* @private
|
||||
* @static
|
||||
*/
|
||||
export function convertBufferToIntArray(buffer: Buffer): number[] {
|
||||
return Array.prototype.slice.call(buffer, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the communication package into 64 byte fragments
|
||||
* @param {UsbCommand} usbCommand
|
||||
* @param {Buffer} configBuffer
|
||||
* @returns {Buffer[]}
|
||||
* @private
|
||||
*/
|
||||
export function getTransferBuffers(usbCommand: UsbCommand, configBuffer: Buffer): Buffer[] {
|
||||
const fragments: Buffer[] = [];
|
||||
const MAX_SENDING_PAYLOAD_SIZE = Constants.MAX_PAYLOAD_SIZE - 4;
|
||||
for (let offset = 0; offset < configBuffer.length; offset += MAX_SENDING_PAYLOAD_SIZE) {
|
||||
const length = offset + MAX_SENDING_PAYLOAD_SIZE < configBuffer.length
|
||||
? MAX_SENDING_PAYLOAD_SIZE
|
||||
: configBuffer.length - offset;
|
||||
const header = new Buffer([usbCommand, length, offset & 0xFF, offset >> 8]);
|
||||
fragments.push(Buffer.concat([header, configBuffer.slice(offset, offset + length)]));
|
||||
}
|
||||
|
||||
return fragments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the communication package that will send over USB and
|
||||
* @param {Buffer} buffer
|
||||
* @returns {number[]}
|
||||
* @private
|
||||
* @static
|
||||
*/
|
||||
export function getTransferData(buffer: Buffer): number[] {
|
||||
const data = convertBufferToIntArray(buffer);
|
||||
data.unshift(0);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert buffer to space separated hexadecimal string
|
||||
* @param {Buffer} buffer
|
||||
* @returns {string}
|
||||
* @private
|
||||
* @static
|
||||
*/
|
||||
export function bufferToString(buffer: Array<number>): string {
|
||||
let str = '';
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
let hex = buffer[i].toString(16) + ' ';
|
||||
if (hex.length <= 2) {
|
||||
hex = '0' + hex;
|
||||
}
|
||||
str += hex;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export async function retry(command: Function, maxTry = 3, logService?: LogService): Promise<any> {
|
||||
let retryCount = 0;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
// logService.debug(`[retry] try to run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
await command();
|
||||
await snooze(100);
|
||||
// logService.debug(`[retry] success FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
return;
|
||||
} catch (err) {
|
||||
retryCount++;
|
||||
if (retryCount >= maxTry) {
|
||||
|
||||
if (logService) {
|
||||
// logService.error(`[retry] failed and no try rerun FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
}
|
||||
|
||||
throw err;
|
||||
} else {
|
||||
if (logService) {
|
||||
logService.error(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2534
packages/uhk-web/package-lock.json
generated
2534
packages/uhk-web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,40 @@
|
||||
<h1>
|
||||
<div class="full-height">
|
||||
<div class="flex-container">
|
||||
<div>
|
||||
|
||||
<h1>
|
||||
<i class="fa fa-sliders"></i>
|
||||
<span>Firmware</span>
|
||||
</h1>
|
||||
<p>
|
||||
Coming soon ...
|
||||
</p>
|
||||
</h1>
|
||||
<p>
|
||||
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
|
||||
<button class="btn btn-primary"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(click)="onUpdateFirmware()">Flash firmware
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Flash firmware file <input id="firmware-file-select"
|
||||
type="file"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(change)="changeFile($event)">
|
||||
<button class="btn btn-primary"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(click)="onUpdateFirmwareWithFile()">Flash firmware
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-grow" #scrollMe>
|
||||
<xterm [logs]="xtermLog$ | async"></xterm>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button type="button"
|
||||
class="btn btn-primary ok-button"
|
||||
[disabled]="firmwareOkButtonDisabled$ | async"
|
||||
(click)="onOkButtonClick()">OK
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,3 +3,25 @@
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-grow {
|
||||
background-color: black;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.ok-button {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { VersionInformation } from 'uhk-common';
|
||||
|
||||
import { AppState, firmwareOkButtonDisabled, flashFirmwareButtonDisbabled, getAgentVersionInfo, xtermLog } from '../../../store';
|
||||
import { UpdateFirmwareAction, UpdateFirmwareOkButtonAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
|
||||
import { XtermLog } from '../../../models/xterm-log';
|
||||
|
||||
@Component({
|
||||
selector: 'device-firmware',
|
||||
@@ -8,5 +16,63 @@ import { Component } from '@angular/core';
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
})
|
||||
export class DeviceFirmwareComponent {
|
||||
export class DeviceFirmwareComponent implements OnDestroy {
|
||||
flashFirmwareButtonDisbabled$: Observable<boolean>;
|
||||
xtermLog$: Observable<Array<XtermLog>>;
|
||||
xtermLogSubscription: Subscription;
|
||||
getAgentVersionInfo$: Observable<VersionInformation>;
|
||||
firmwareOkButtonDisabled$: Observable<boolean>;
|
||||
|
||||
arrayBuffer: Uint8Array;
|
||||
@ViewChild('scrollMe') divElement: ElementRef;
|
||||
|
||||
constructor(private store: Store<AppState>) {
|
||||
this.flashFirmwareButtonDisbabled$ = store.select(flashFirmwareButtonDisbabled);
|
||||
this.xtermLog$ = store.select(xtermLog);
|
||||
this.xtermLogSubscription = this.xtermLog$.subscribe(() => {
|
||||
if (this.divElement && this.divElement.nativeElement) {
|
||||
setTimeout(() => {
|
||||
this.divElement.nativeElement.scrollTop = this.divElement.nativeElement.scrollHeight;
|
||||
});
|
||||
}
|
||||
});
|
||||
this.getAgentVersionInfo$ = store.select(getAgentVersionInfo);
|
||||
this.firmwareOkButtonDisabled$ = store.select(firmwareOkButtonDisabled);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.xtermLogSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
onUpdateFirmware(): void {
|
||||
this.store.dispatch(new UpdateFirmwareAction());
|
||||
}
|
||||
|
||||
onUpdateFirmwareWithFile(): void {
|
||||
if (!this.arrayBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(this.arrayBuffer)));
|
||||
}
|
||||
|
||||
onOkButtonClick(): void {
|
||||
this.store.dispatch(new UpdateFirmwareOkButtonAction());
|
||||
}
|
||||
|
||||
changeFile(event): void {
|
||||
const files = event.srcElement.files;
|
||||
|
||||
if (files.length === 0) {
|
||||
this.arrayBuffer = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onloadend = function () {
|
||||
this.arrayBuffer = new Uint8Array(fileReader.result);
|
||||
}.bind(this);
|
||||
fileReader.readAsArrayBuffer(files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,17 +19,20 @@
|
||||
<ul [@toggler]="animation['configuration']">
|
||||
<li class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/device/mouse-speed']">Mouse speed</a>
|
||||
<a [routerLink]="['/device/mouse-speed']"
|
||||
[class.disabled]="updatingFirmware$ | async">Mouse speed</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/device/configuration']">Configuration</a>
|
||||
<a [routerLink]="['/device/configuration']"
|
||||
[class.disabled]="updatingFirmware$ | async">Configuration</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/device/firmware']">Firmware</a>
|
||||
<a [routerLink]="['/device/firmware']"
|
||||
[class.disabled]="updatingFirmware$ | async">Firmware</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -37,16 +40,21 @@
|
||||
<li class="sidebar__level-1--item">
|
||||
<div class="sidebar__level-1">
|
||||
<i class="fa fa-keyboard-o"></i> Keymaps
|
||||
<a [routerLink]="['/keymap/add']" class="btn btn-default pull-right btn-sm">
|
||||
<a [routerLink]="['/keymap/add']"
|
||||
class="btn btn-default pull-right btn-sm"
|
||||
[class.disabled]="updatingFirmware$ | async">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'keymap')"></i>
|
||||
<i class="fa fa-chevron-up pull-right"
|
||||
(click)="toggleHide($event, 'keymap')"></i>
|
||||
</div>
|
||||
<ul [@toggler]="animation['keymap']">
|
||||
<li *ngFor="let keymap of keymaps$ | async" class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/keymap', keymap.abbreviation]">{{keymap.name}}</a>
|
||||
<i *ngIf="keymap.isDefault" class="fa fa-star sidebar__fav" title="This is the default keymap which gets activated when powering the keyboard."
|
||||
<a [routerLink]="['/keymap', keymap.abbreviation]"
|
||||
[class.disabled]="updatingFirmware$ | async">{{keymap.name}}</a>
|
||||
<i *ngIf="keymap.isDefault" class="fa fa-star sidebar__fav"
|
||||
title="This is the default keymap which gets activated when powering the keyboard."
|
||||
data-toggle="tooltip" data-placement="bottom"></i>
|
||||
</div>
|
||||
</li>
|
||||
@@ -55,7 +63,9 @@
|
||||
<li class="sidebar__level-1--item">
|
||||
<div class="sidebar__level-1">
|
||||
<i class="fa fa-play"></i> Macros
|
||||
<a (click)="addMacro()" class="btn btn-default pull-right btn-sm">
|
||||
<a (click)="addMacro()"
|
||||
class="btn btn-default pull-right btn-sm"
|
||||
[class.disabled]="updatingFirmware$ | async">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'macro')"></i>
|
||||
@@ -63,7 +73,8 @@
|
||||
<ul [@toggler]="animation['macro']">
|
||||
<li *ngFor="let macro of macros$ | async" class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/macro', macro.id]">{{macro.name}}</a>
|
||||
<a [routerLink]="['/macro', macro.id]"
|
||||
[class.disabled]="updatingFirmware$ | async">{{macro.name}}</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -76,22 +87,26 @@
|
||||
<ul [@toggler]="animation['addon']">
|
||||
<li class="sidebar__level-2--item" data-name="Key cluster" data-abbrev="">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/add-on', 'Key cluster']">Key cluster</a>
|
||||
<a [routerLink]="['/add-on', 'Key cluster']"
|
||||
[class.disabled]="updatingFirmware$ | async">Key cluster</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar__level-2--item" data-name="Trackball" data-abbrev="">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/add-on', 'Trackball']">Trackball</a>
|
||||
<a [routerLink]="['/add-on', 'Trackball']"
|
||||
[class.disabled]="updatingFirmware$ | async">Trackball</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar__level-2--item" data-name="Toucpad" data-abbrev="">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/add-on', 'Touchpad']">Touchpad</a>
|
||||
<a [routerLink]="['/add-on', 'Touchpad']"
|
||||
[class.disabled]="updatingFirmware$ | async">Touchpad</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar__level-2--item" data-name="Trackpoint" data-abbrev="">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/add-on', 'Trackpoint']">Trackpoint</a>
|
||||
<a [routerLink]="['/add-on', 'Trackpoint']"
|
||||
[class.disabled]="updatingFirmware$ | async">Trackpoint</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { AfterContentInit, Component, ElementRef, Renderer2, ViewChild } from '@angular/core';
|
||||
import { AfterContentInit, Component, ElementRef, OnDestroy, Renderer2, ViewChild } from '@angular/core';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { Keymap, Macro } from 'uhk-common';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/let';
|
||||
|
||||
import { AppState, getDeviceName, showAddonMenu, runningInElectron } from '../../store';
|
||||
import { AppState, getDeviceName, runningInElectron, showAddonMenu, updatingFirmware } from '../../store';
|
||||
import { MacroActions } from '../../store/actions';
|
||||
import { getKeymaps, getMacros } from '../../store/reducers/user-configuration';
|
||||
import * as util from '../../util';
|
||||
@@ -31,15 +32,19 @@ import { RenameUserConfigurationAction } from '../../store/actions/user-config';
|
||||
templateUrl: './side-menu.component.html',
|
||||
styleUrls: ['./side-menu.component.scss']
|
||||
})
|
||||
export class SideMenuComponent implements AfterContentInit {
|
||||
export class SideMenuComponent implements AfterContentInit, OnDestroy {
|
||||
showAddonMenu$: Observable<boolean>;
|
||||
runInElectron$: Observable<boolean>;
|
||||
updatingFirmware$: Observable<boolean>;
|
||||
|
||||
deviceName$: Observable<string>;
|
||||
deviceNameSubscription: Subscription;
|
||||
keymaps$: Observable<Keymap[]>;
|
||||
macros$: Observable<Macro[]>;
|
||||
animation: { [key: string]: 'active' | 'inactive' };
|
||||
deviceNameValue: string;
|
||||
updatingFirmware = false;
|
||||
updatingFirmwareSubscription: Subscription;
|
||||
@ViewChild('deviceName') deviceName: ElementRef;
|
||||
|
||||
constructor(private store: Store<AppState>, private renderer: Renderer2) {
|
||||
@@ -66,17 +71,30 @@ export class SideMenuComponent implements AfterContentInit {
|
||||
this.showAddonMenu$ = this.store.select(showAddonMenu);
|
||||
this.runInElectron$ = this.store.select(runningInElectron);
|
||||
this.deviceName$ = store.select(getDeviceName);
|
||||
this.deviceName$.subscribe(name => {
|
||||
this.deviceNameSubscription = this.deviceName$.subscribe(name => {
|
||||
this.deviceNameValue = name;
|
||||
this.setDeviceName();
|
||||
});
|
||||
this.updatingFirmware$ = store.select(updatingFirmware);
|
||||
this.updatingFirmwareSubscription = this.updatingFirmware$.subscribe(updating => {
|
||||
this.updatingFirmware = updating;
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
this.setDeviceName();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.deviceNameSubscription.unsubscribe();
|
||||
this.updatingFirmwareSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
toggleHide(event: Event, type: string) {
|
||||
if (this.updatingFirmware) {
|
||||
return;
|
||||
}
|
||||
|
||||
const header: DOMTokenList = (<Element>event.target).classList;
|
||||
let show = false;
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="wrapper">
|
||||
<ul class="list-unstyled">
|
||||
<li *ngFor="let log of logs" [ngClass]="log.cssClass"><span>{{ log.message }}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
:host {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.xterm-standard {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.xterm-error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
ul {
|
||||
li {
|
||||
padding-left: 5px;
|
||||
span:before {
|
||||
content: '$ ';
|
||||
}
|
||||
}
|
||||
}
|
||||
12
packages/uhk-web/src/app/components/xterm/xterm.component.ts
Normal file
12
packages/uhk-web/src/app/components/xterm/xterm.component.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { XtermLog } from '../../models/xterm-log';
|
||||
|
||||
@Component({
|
||||
selector: 'xterm',
|
||||
templateUrl: './xterm.component.html',
|
||||
styleUrls: ['./xterm.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class XtermComponent {
|
||||
@Input() logs: Array<XtermLog> = [];
|
||||
}
|
||||
14
packages/uhk-web/src/app/models/xterm-log.ts
Normal file
14
packages/uhk-web/src/app/models/xterm-log.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface XtermLog {
|
||||
message: string;
|
||||
cssClass: XtermCssClass;
|
||||
}
|
||||
|
||||
export enum XtermCssClass {
|
||||
standard = 'xterm-standard',
|
||||
error = 'xterm-error'
|
||||
}
|
||||
|
||||
export interface ElectronLogEntry {
|
||||
level: string;
|
||||
message: string;
|
||||
}
|
||||
@@ -2,7 +2,8 @@ import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'main-page',
|
||||
templateUrl: './main.page.html'
|
||||
templateUrl: './main.page.html',
|
||||
styles: [':host{height:100%; display: inline-block; width: 100%}']
|
||||
})
|
||||
export class MainPage {
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Injectable, NgZone } from '@angular/core';
|
||||
import { Action, Store } from '@ngrx/store';
|
||||
|
||||
import { IpcEvents, AppStartInfo, LogService } from 'uhk-common';
|
||||
import { AppState } from '../store/index';
|
||||
import { ProcessAppStartInfoAction } from '../store/actions/app';
|
||||
import { AppStartInfo, IpcEvents, LogService } from 'uhk-common';
|
||||
import { AppState } from '../store';
|
||||
import { ElectronMainLogReceivedAction, ProcessAppStartInfoAction } from '../store/actions/app';
|
||||
import { IpcCommonRenderer } from './ipc-common-renderer';
|
||||
|
||||
@Injectable()
|
||||
@@ -25,6 +25,10 @@ export class AppRendererService {
|
||||
this.ipcRenderer.on(IpcEvents.app.getAppStartInfoReply, (event: string, arg: AppStartInfo) => {
|
||||
this.dispachStoreAction(new ProcessAppStartInfoAction(arg));
|
||||
});
|
||||
|
||||
this.ipcRenderer.on('__ELECTRON_LOG_RENDERER__', (event: string, level: string, message: string) => {
|
||||
this.zone.run(() => this.store.dispatch(new ElectronMainLogReceivedAction({level, message})));
|
||||
});
|
||||
}
|
||||
|
||||
private dispachStoreAction(action: Action) {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Injectable, NgZone } from '@angular/core';
|
||||
import { Action, Store } from '@ngrx/store';
|
||||
|
||||
import { IpcEvents, LogService, IpcResponse } from 'uhk-common';
|
||||
import { AppState } from '../store/index';
|
||||
import { IpcEvents, IpcResponse, LogService } from 'uhk-common';
|
||||
import { AppState } from '../store';
|
||||
import { IpcCommonRenderer } from './ipc-common-renderer';
|
||||
import {
|
||||
ConnectionStateChangedAction,
|
||||
SaveConfigurationReplyAction,
|
||||
SetPrivilegeOnLinuxReplyAction
|
||||
SetPrivilegeOnLinuxReplyAction,
|
||||
UpdateFirmwareReplyAction
|
||||
} from '../store/actions/device';
|
||||
import { LoadConfigFromDeviceReplyAction } from '../store/actions/user-config';
|
||||
|
||||
@@ -33,6 +34,14 @@ export class DeviceRendererService {
|
||||
this.ipcRenderer.send(IpcEvents.device.loadConfigurations);
|
||||
}
|
||||
|
||||
updateFirmware(data?: Array<number>): void {
|
||||
this.ipcRenderer.send(IpcEvents.device.updateFirmware, JSON.stringify(data));
|
||||
}
|
||||
|
||||
startConnectionPoller(): void {
|
||||
this.ipcRenderer.send(IpcEvents.device.startConnectionPoller);
|
||||
}
|
||||
|
||||
private registerEvents(): void {
|
||||
this.ipcRenderer.on(IpcEvents.device.deviceConnectionStateChanged, (event: string, arg: boolean) => {
|
||||
this.dispachStoreAction(new ConnectionStateChangedAction(arg));
|
||||
@@ -49,10 +58,14 @@ export class DeviceRendererService {
|
||||
this.ipcRenderer.on(IpcEvents.device.loadConfigurationReply, (event: string, response: string) => {
|
||||
this.dispachStoreAction(new LoadConfigFromDeviceReplyAction(JSON.parse(response)));
|
||||
});
|
||||
|
||||
this.ipcRenderer.on(IpcEvents.device.updateFirmwareReply, (event: string, response: IpcResponse) => {
|
||||
this.dispachStoreAction(new UpdateFirmwareReplyAction(response));
|
||||
});
|
||||
}
|
||||
|
||||
private dispachStoreAction(action: Action): void {
|
||||
this.logService.info('[DeviceRendererService] dispatch action', action);
|
||||
this.logService.info('[DeviceRendererService] dispatch action', JSON.stringify(action));
|
||||
this.zone.run(() => this.store.dispatch(action));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ import { MainAppComponent } from './app.component';
|
||||
import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.page';
|
||||
import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
|
||||
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
|
||||
import { XtermComponent } from './components/xterm/xterm.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -161,7 +162,8 @@ import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
|
||||
PrivilegeCheckerComponent,
|
||||
MainPage,
|
||||
ProgressButtonComponent,
|
||||
LoadingDevicePageComponent
|
||||
LoadingDevicePageComponent,
|
||||
XtermComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { type } from 'uhk-common';
|
||||
import { AppStartInfo, HardwareConfiguration, Notification } from 'uhk-common';
|
||||
import { AppStartInfo, HardwareConfiguration, Notification, type, VersionInformation } from 'uhk-common';
|
||||
import { ElectronLogEntry } from '../../models/xterm-log';
|
||||
|
||||
const PREFIX = '[app] ';
|
||||
|
||||
@@ -15,7 +15,9 @@ export const ActionTypes = {
|
||||
UNDO_LAST: type(PREFIX + 'undo last action'),
|
||||
UNDO_LAST_SUCCESS: type(PREFIX + 'undo last action success'),
|
||||
DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action'),
|
||||
LOAD_HARDWARE_CONFIGURATION_SUCCESS: type(PREFIX + 'load hardware configuration success')
|
||||
LOAD_HARDWARE_CONFIGURATION_SUCCESS: type(PREFIX + 'load hardware configuration success'),
|
||||
UPDATE_AGENT_VERSION_INFORMATION: type(PREFIX + 'update agent version information'),
|
||||
ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received')
|
||||
};
|
||||
|
||||
export class AppBootsrappedAction implements Action {
|
||||
@@ -64,6 +66,18 @@ export class LoadHardwareConfigurationSuccessAction implements Action {
|
||||
constructor(public payload: HardwareConfiguration) {}
|
||||
}
|
||||
|
||||
export class UpdateAgentVersionInformationAction implements Action {
|
||||
type = ActionTypes.UPDATE_AGENT_VERSION_INFORMATION;
|
||||
|
||||
constructor(public payload: VersionInformation) {}
|
||||
}
|
||||
|
||||
export class ElectronMainLogReceivedAction implements Action {
|
||||
type = ActionTypes.ELECTRON_MAIN_LOG_RECEIVED;
|
||||
|
||||
constructor(public payload: ElectronLogEntry) {}
|
||||
}
|
||||
|
||||
export type Actions
|
||||
= AppStartedAction
|
||||
| AppBootsrappedAction
|
||||
@@ -74,4 +88,6 @@ export type Actions
|
||||
| UndoLastSuccessAction
|
||||
| DismissUndoNotificationAction
|
||||
| LoadHardwareConfigurationSuccessAction
|
||||
| UpdateAgentVersionInformationAction
|
||||
| ElectronMainLogReceivedAction
|
||||
;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { type, IpcResponse } from 'uhk-common';
|
||||
import { IpcResponse, type } from 'uhk-common';
|
||||
|
||||
const PREFIX = '[device] ';
|
||||
|
||||
@@ -16,7 +16,13 @@ export const ActionTypes = {
|
||||
SAVE_TO_KEYBOARD_SUCCESS: type(PREFIX + 'save to keyboard success'),
|
||||
SAVE_TO_KEYBOARD_FAILED: type(PREFIX + 'save to keyboard failed'),
|
||||
HIDE_SAVE_TO_KEYBOARD_BUTTON: type(PREFIX + 'hide save to keyboard button'),
|
||||
RESET_USER_CONFIGURATION: type(PREFIX + 'reset user configuration')
|
||||
RESET_USER_CONFIGURATION: type(PREFIX + 'reset user configuration'),
|
||||
UPDATE_FIRMWARE: type(PREFIX + 'update firmware'),
|
||||
UPDATE_FIRMWARE_WITH: type(PREFIX + 'update firmware with'),
|
||||
UPDATE_FIRMWARE_REPLY: type(PREFIX + 'update firmware reply'),
|
||||
UPDATE_FIRMWARE_SUCCESS: type(PREFIX + 'update firmware success'),
|
||||
UPDATE_FIRMWARE_FAILED: type(PREFIX + 'update firmware failed'),
|
||||
UPDATE_FIRMWARE_OK_BUTTON: type(PREFIX + 'update firmware ok button click')
|
||||
};
|
||||
|
||||
export class SetPrivilegeOnLinuxAction implements Action {
|
||||
@@ -26,31 +32,36 @@ export class SetPrivilegeOnLinuxAction implements Action {
|
||||
export class SetPrivilegeOnLinuxReplyAction implements Action {
|
||||
type = ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY;
|
||||
|
||||
constructor(public payload: IpcResponse) {}
|
||||
constructor(public payload: IpcResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionStateChangedAction implements Action {
|
||||
type = ActionTypes.CONNECTION_STATE_CHANGED;
|
||||
|
||||
constructor(public payload: boolean) {}
|
||||
constructor(public payload: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
export class PermissionStateChangedAction implements Action {
|
||||
type = ActionTypes.PERMISSION_STATE_CHANGED;
|
||||
|
||||
constructor(public payload: boolean) {}
|
||||
constructor(public payload: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
export class SaveConfigurationAction implements Action {
|
||||
type = ActionTypes.SAVE_CONFIGURATION;
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
|
||||
export class SaveConfigurationReplyAction implements Action {
|
||||
type = ActionTypes.SAVE_CONFIGURATION_REPLY;
|
||||
|
||||
constructor(public payload: IpcResponse) {}
|
||||
constructor(public payload: IpcResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowSaveToKeyboardButtonAction implements Action {
|
||||
@@ -73,6 +84,39 @@ export class ResetUserConfigurationAction implements Action {
|
||||
type = ActionTypes.RESET_USER_CONFIGURATION;
|
||||
}
|
||||
|
||||
export class UpdateFirmwareAction implements Action {
|
||||
type = ActionTypes.UPDATE_FIRMWARE;
|
||||
}
|
||||
|
||||
export class UpdateFirmwareWithAction implements Action {
|
||||
type = ActionTypes.UPDATE_FIRMWARE_WITH;
|
||||
|
||||
constructor(public payload: Array<number>) {
|
||||
}
|
||||
}
|
||||
|
||||
export class UpdateFirmwareReplyAction implements Action {
|
||||
type = ActionTypes.UPDATE_FIRMWARE_REPLY;
|
||||
|
||||
constructor(public payload: IpcResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
export class UpdateFirmwareSuccessAction implements Action {
|
||||
type = ActionTypes.UPDATE_FIRMWARE_SUCCESS;
|
||||
}
|
||||
|
||||
export class UpdateFirmwareFailedAction implements Action {
|
||||
type = ActionTypes.UPDATE_FIRMWARE_FAILED;
|
||||
|
||||
constructor(public payload: any) {
|
||||
}
|
||||
}
|
||||
|
||||
export class UpdateFirmwareOkButtonAction implements Action {
|
||||
type = ActionTypes.UPDATE_FIRMWARE_OK_BUTTON;
|
||||
}
|
||||
|
||||
export type Actions
|
||||
= SetPrivilegeOnLinuxAction
|
||||
| SetPrivilegeOnLinuxReplyAction
|
||||
@@ -85,4 +129,10 @@ export type Actions
|
||||
| SaveToKeyboardSuccessFailed
|
||||
| HideSaveToKeyboardButton
|
||||
| ResetUserConfigurationAction
|
||||
| UpdateFirmwareAction
|
||||
| UpdateFirmwareWithAction
|
||||
| UpdateFirmwareReplyAction
|
||||
| UpdateFirmwareSuccessAction
|
||||
| UpdateFirmwareFailedAction
|
||||
| UpdateFirmwareOkButtonAction
|
||||
;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Action } from '@ngrx/store';
|
||||
import { Actions, Effect, toPayload } from '@ngrx/effects';
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { NotifierService } from 'angular-notifier';
|
||||
|
||||
@@ -10,8 +10,16 @@ import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/mergeMap';
|
||||
import 'rxjs/add/operator/catch';
|
||||
|
||||
import { AppStartInfo, Notification, NotificationType, LogService } from 'uhk-common';
|
||||
import { ActionTypes, AppStartedAction, DismissUndoNotificationAction, ToggleAddonMenuAction } from '../actions/app';
|
||||
import { AppStartInfo, LogService, Notification, NotificationType } from 'uhk-common';
|
||||
import {
|
||||
ActionTypes,
|
||||
AppStartedAction,
|
||||
DismissUndoNotificationAction,
|
||||
ProcessAppStartInfoAction,
|
||||
ShowNotificationAction,
|
||||
ToggleAddonMenuAction,
|
||||
UndoLastAction, UpdateAgentVersionInformationAction
|
||||
} from '../actions/app';
|
||||
import { AppRendererService } from '../../services/app-renderer.service';
|
||||
import { AppUpdateRendererService } from '../../services/app-update-renderer.service';
|
||||
import { ConnectionStateChangedAction, PermissionStateChangedAction } from '../actions/device';
|
||||
@@ -32,8 +40,8 @@ export class ApplicationEffects {
|
||||
|
||||
@Effect({dispatch: false})
|
||||
showNotification$: Observable<Action> = this.actions$
|
||||
.ofType(ActionTypes.APP_SHOW_NOTIFICATION)
|
||||
.map(toPayload)
|
||||
.ofType<ShowNotificationAction>(ActionTypes.APP_SHOW_NOTIFICATION)
|
||||
.map(action => action.payload)
|
||||
.do((notification: Notification) => {
|
||||
if (notification.type === NotificationType.Undoable) {
|
||||
return;
|
||||
@@ -43,25 +51,27 @@ export class ApplicationEffects {
|
||||
|
||||
@Effect()
|
||||
processStartInfo$: Observable<Action> = this.actions$
|
||||
.ofType(ActionTypes.APP_PROCESS_START_INFO)
|
||||
.map(toPayload)
|
||||
.ofType<ProcessAppStartInfoAction>(ActionTypes.APP_PROCESS_START_INFO)
|
||||
.map(action => action.payload)
|
||||
.mergeMap((appInfo: AppStartInfo) => {
|
||||
this.logService.debug('[AppEffect][processStartInfo] payload:', appInfo);
|
||||
return [
|
||||
new ToggleAddonMenuAction(appInfo.commandLineArgs.addons),
|
||||
new ConnectionStateChangedAction(appInfo.deviceConnected),
|
||||
new PermissionStateChangedAction(appInfo.hasPermission)
|
||||
new PermissionStateChangedAction(appInfo.hasPermission),
|
||||
new UpdateAgentVersionInformationAction(appInfo.agentVersionInfo)
|
||||
];
|
||||
});
|
||||
|
||||
@Effect() undoLastNotification$: Observable<Action> = this.actions$
|
||||
.ofType(ActionTypes.UNDO_LAST)
|
||||
.map(toPayload)
|
||||
.ofType<UndoLastAction>(ActionTypes.UNDO_LAST)
|
||||
.map(action => action.payload)
|
||||
.mergeMap((action: Action) => [action, new DismissUndoNotificationAction()]);
|
||||
|
||||
constructor(private actions$: Actions,
|
||||
private notifierService: NotifierService,
|
||||
private appUpdateRendererService: AppUpdateRendererService,
|
||||
private appRendererService: AppRendererService,
|
||||
private logService: LogService) { }
|
||||
private logService: LogService) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'rxjs/add/operator/mergeMap';
|
||||
import 'rxjs/add/operator/withLatestFrom';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
|
||||
import { NotificationType, IpcResponse, UhkBuffer, UserConfiguration } from 'uhk-common';
|
||||
import { IpcResponse, NotificationType, UhkBuffer, UserConfiguration } from 'uhk-common';
|
||||
import {
|
||||
ActionTypes,
|
||||
ConnectionStateChangedAction,
|
||||
@@ -21,15 +21,22 @@ import {
|
||||
PermissionStateChangedAction,
|
||||
SaveConfigurationAction,
|
||||
SaveToKeyboardSuccessAction,
|
||||
SaveToKeyboardSuccessFailed
|
||||
SaveToKeyboardSuccessFailed,
|
||||
SetPrivilegeOnLinuxReplyAction,
|
||||
UpdateFirmwareAction,
|
||||
UpdateFirmwareFailedAction,
|
||||
UpdateFirmwareOkButtonAction,
|
||||
UpdateFirmwareReplyAction,
|
||||
UpdateFirmwareSuccessAction,
|
||||
UpdateFirmwareWithAction
|
||||
} from '../actions/device';
|
||||
import { DeviceRendererService } from '../../services/device-renderer.service';
|
||||
import { ShowNotificationAction } from '../actions/app';
|
||||
import { AppState } from '../index';
|
||||
import {
|
||||
ActionTypes as UserConfigActions,
|
||||
LoadConfigFromDeviceAction,
|
||||
LoadResetUserConfigurationAction,
|
||||
ActionTypes as UserConfigActions
|
||||
LoadResetUserConfigurationAction
|
||||
} from '../actions/user-config';
|
||||
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
|
||||
|
||||
@@ -37,8 +44,8 @@ import { DefaultUserConfigurationService } from '../../services/default-user-con
|
||||
export class DeviceEffects {
|
||||
@Effect()
|
||||
deviceConnectionStateChange$: Observable<Action> = this.actions$
|
||||
.ofType(ActionTypes.CONNECTION_STATE_CHANGED)
|
||||
.map(toPayload)
|
||||
.ofType<ConnectionStateChangedAction>(ActionTypes.CONNECTION_STATE_CHANGED)
|
||||
.map(action => action.payload)
|
||||
.do((connected: boolean) => {
|
||||
if (connected) {
|
||||
this.router.navigate(['/']);
|
||||
@@ -56,9 +63,9 @@ export class DeviceEffects {
|
||||
});
|
||||
|
||||
@Effect({dispatch: false})
|
||||
permissionStateChange$: Observable<Action> = this.actions$
|
||||
.ofType(ActionTypes.PERMISSION_STATE_CHANGED)
|
||||
.map(toPayload)
|
||||
permissionStateChange$: Observable<boolean> = this.actions$
|
||||
.ofType<PermissionStateChangedAction>(ActionTypes.PERMISSION_STATE_CHANGED)
|
||||
.map(action => action.payload)
|
||||
.do((hasPermission: boolean) => {
|
||||
if (hasPermission) {
|
||||
this.router.navigate(['/detection']);
|
||||
@@ -77,8 +84,8 @@ export class DeviceEffects {
|
||||
|
||||
@Effect()
|
||||
setPrivilegeOnLinuxReply$: Observable<Action> = this.actions$
|
||||
.ofType(ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY)
|
||||
.map(toPayload)
|
||||
.ofType<SetPrivilegeOnLinuxReplyAction>(ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY)
|
||||
.map(action => action.payload)
|
||||
.mergeMap((response: any) => {
|
||||
if (response.success) {
|
||||
return [
|
||||
@@ -142,6 +149,30 @@ export class DeviceEffects {
|
||||
.ofType(UserConfigActions.LOAD_RESET_USER_CONFIGURATION)
|
||||
.switchMap(() => Observable.of(new SaveConfigurationAction()));
|
||||
|
||||
@Effect({dispatch: false}) updateFirmware$ = this.actions$
|
||||
.ofType<UpdateFirmwareAction>(ActionTypes.UPDATE_FIRMWARE)
|
||||
.do(() => this.deviceRendererService.updateFirmware());
|
||||
|
||||
@Effect({dispatch: false}) updateFirmwareWith$ = this.actions$
|
||||
.ofType<UpdateFirmwareWithAction>(ActionTypes.UPDATE_FIRMWARE_WITH)
|
||||
.map(action => action.payload)
|
||||
.do(data => this.deviceRendererService.updateFirmware(data));
|
||||
|
||||
@Effect() updateFirmwareReply$ = this.actions$
|
||||
.ofType<UpdateFirmwareReplyAction>(ActionTypes.UPDATE_FIRMWARE_REPLY)
|
||||
.map(action => action.payload)
|
||||
.switchMap((response: IpcResponse) => {
|
||||
if (response.success) {
|
||||
return Observable.of(new UpdateFirmwareSuccessAction());
|
||||
}
|
||||
|
||||
return Observable.of(new UpdateFirmwareFailedAction(response.error));
|
||||
});
|
||||
|
||||
@Effect({dispatch: false}) updateFirmwareOkButton$ = this.actions$
|
||||
.ofType<UpdateFirmwareOkButtonAction>(ActionTypes.UPDATE_FIRMWARE_OK_BUTTON)
|
||||
.do(() => this.deviceRendererService.startConnectionPoller());
|
||||
|
||||
constructor(private actions$: Actions,
|
||||
private router: Router,
|
||||
private deviceRendererService: DeviceRendererService,
|
||||
|
||||
@@ -49,6 +49,7 @@ export const runningInElectron = createSelector(appState, fromApp.runningInElect
|
||||
export const getHardwareConfiguration = createSelector(appState, fromApp.getHardwareConfiguration);
|
||||
export const getKeyboardLayout = createSelector(appState, fromApp.getKeyboardLayout);
|
||||
export const deviceConfigurationLoaded = createSelector(appState, fromApp.deviceConfigurationLoaded);
|
||||
export const getAgentVersionInfo = createSelector(appState, fromApp.getAgentVersionInfo);
|
||||
|
||||
export const appUpdateState = (state: AppState) => state.appUpdate;
|
||||
export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable);
|
||||
@@ -70,3 +71,8 @@ export const saveToKeyboardStateSelector = createSelector(deviceState, fromDevic
|
||||
export const saveToKeyboardState = createSelector(runningInElectron, saveToKeyboardStateSelector, (electron, state) => {
|
||||
return electron ? state : initProgressButtonState;
|
||||
});
|
||||
export const updatingFirmware = createSelector(deviceState, fromDevice.updatingFirmware);
|
||||
export const xtermLog = createSelector(deviceState, fromDevice.xtermLog);
|
||||
export const firmwareOkButtonDisabled = createSelector(deviceState, fromDevice.firmwareOkButtonDisabled);
|
||||
// tslint:disable-next-line: max-line-length
|
||||
export const flashFirmwareButtonDisbabled = createSelector(runningInElectron, deviceState, (electron, state: fromDevice.State) => !electron || state.updatingFirmware);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ROUTER_NAVIGATION } from '@ngrx/router-store';
|
||||
import { Action } from '@ngrx/store';
|
||||
import { VersionInformation } from 'uhk-common';
|
||||
|
||||
import { HardwareConfiguration, runInElectron, Notification, NotificationType, UserConfiguration } from 'uhk-common';
|
||||
import { HardwareConfiguration, Notification, NotificationType, runInElectron, UserConfiguration } from 'uhk-common';
|
||||
import { ActionTypes, ShowNotificationAction } from '../actions/app';
|
||||
import { ActionTypes as UserConfigActionTypes } from '../actions/user-config';
|
||||
import { ActionTypes as DeviceActionTypes } from '../actions/device';
|
||||
@@ -16,6 +17,7 @@ export interface State {
|
||||
runningInElectron: boolean;
|
||||
configLoading: boolean;
|
||||
hardwareConfig?: HardwareConfiguration;
|
||||
agentVersionInfo?: VersionInformation;
|
||||
}
|
||||
|
||||
export const initialState: State = {
|
||||
@@ -99,7 +101,7 @@ export function reducer(state = initialState, action: Action & { payload: any })
|
||||
hardwareConfig: action.payload
|
||||
};
|
||||
|
||||
case DeviceActionTypes.CONNECTION_STATE_CHANGED:
|
||||
case DeviceActionTypes.CONNECTION_STATE_CHANGED: {
|
||||
|
||||
if (action.payload === true) {
|
||||
return state;
|
||||
@@ -109,7 +111,13 @@ export function reducer(state = initialState, action: Action & { payload: any })
|
||||
...state,
|
||||
hardwareConfig: null
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.UPDATE_AGENT_VERSION_INFORMATION:
|
||||
return {
|
||||
...state,
|
||||
agentVersionInfo: action.payload
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -128,3 +136,4 @@ export const getKeyboardLayout = (state: State): KeyboardLayout => {
|
||||
return KeyboardLayout.ANSI;
|
||||
};
|
||||
export const deviceConfigurationLoaded = (state: State) => !state.runningInElectron ? true : !!state.hardwareConfig;
|
||||
export const getAgentVersionInfo = (state: State) => state.agentVersionInfo || {} as VersionInformation;
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import {
|
||||
ActionTypes, ConnectionStateChangedAction, HideSaveToKeyboardButton, PermissionStateChangedAction,
|
||||
SaveConfigurationAction
|
||||
ActionTypes,
|
||||
ConnectionStateChangedAction,
|
||||
PermissionStateChangedAction,
|
||||
SaveConfigurationAction, UpdateFirmwareFailedAction
|
||||
} from '../actions/device';
|
||||
import { ActionTypes as AppActions, ElectronMainLogReceivedAction } from '../actions/app';
|
||||
import { initProgressButtonState, ProgressButtonState } from './progress-button-state';
|
||||
import { XtermCssClass, XtermLog } from '../../models/xterm-log';
|
||||
|
||||
export interface State {
|
||||
connected: boolean;
|
||||
hasPermission: boolean;
|
||||
saveToKeyboard: ProgressButtonState;
|
||||
updatingFirmware: boolean;
|
||||
firmwareUpdateFinished: boolean;
|
||||
log: Array<XtermLog>;
|
||||
}
|
||||
|
||||
export const initialState: State = {
|
||||
connected: true,
|
||||
hasPermission: true,
|
||||
saveToKeyboard: initProgressButtonState
|
||||
saveToKeyboard: initProgressButtonState,
|
||||
updatingFirmware: false,
|
||||
firmwareUpdateFinished: false,
|
||||
log: [{message: '', cssClass: XtermCssClass.standard}]
|
||||
};
|
||||
|
||||
export function reducer(state = initialState, action: Action) {
|
||||
@@ -89,11 +99,66 @@ export function reducer(state = initialState, action: Action) {
|
||||
saveToKeyboard: initProgressButtonState
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.UPDATE_FIRMWARE_WITH:
|
||||
case ActionTypes.UPDATE_FIRMWARE:
|
||||
return {
|
||||
...state,
|
||||
updatingFirmware: true,
|
||||
firmwareUpdateFinished: false,
|
||||
log: [{message: 'Start flashing firmware', cssClass: XtermCssClass.standard}]
|
||||
};
|
||||
|
||||
case ActionTypes.UPDATE_FIRMWARE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
updatingFirmware: false,
|
||||
firmwareUpdateFinished: true
|
||||
};
|
||||
|
||||
case ActionTypes.UPDATE_FIRMWARE_FAILED: {
|
||||
const logEntry = {
|
||||
message: (action as UpdateFirmwareFailedAction).payload.message,
|
||||
cssClass: XtermCssClass.error
|
||||
};
|
||||
|
||||
return {
|
||||
...state,
|
||||
updatingFirmware: false,
|
||||
firmwareUpdateFinished: true,
|
||||
log: [...state.log, logEntry]
|
||||
};
|
||||
}
|
||||
|
||||
case AppActions.ELECTRON_MAIN_LOG_RECEIVED: {
|
||||
if (!state.updatingFirmware) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const payload = (action as ElectronMainLogReceivedAction).payload;
|
||||
|
||||
if (payload.message.indexOf('UHK Device not found:') > -1) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const logEntry = {
|
||||
message: payload.message,
|
||||
cssClass: payload.level === 'error' ? XtermCssClass.error : XtermCssClass.standard
|
||||
};
|
||||
|
||||
return {
|
||||
...state,
|
||||
log: [...state.log, logEntry]
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export const isDeviceConnected = (state: State) => state.connected;
|
||||
export const updatingFirmware = (state: State) => state.updatingFirmware;
|
||||
export const isDeviceConnected = (state: State) => state.connected || state.updatingFirmware;
|
||||
export const hasDevicePermission = (state: State) => state.hasPermission;
|
||||
export const getSaveToKeyboardState = (state: State) => state.saveToKeyboard;
|
||||
export const xtermLog = (state: State) => state.log;
|
||||
export const firmwareOkButtonDisabled = (state: State) => !state.firmwareUpdateFinished;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import * as log from 'electron-log';
|
||||
import * as util from 'util';
|
||||
import { LogService, LogRegExps } from 'uhk-common';
|
||||
import { LogRegExps, LogService } from 'uhk-common';
|
||||
|
||||
// https://github.com/megahertz/electron-log/issues/44
|
||||
// console.debug starting with Chromium 58 this method is a no-op on Chromium browsers.
|
||||
@@ -13,7 +13,7 @@ if (console.debug) {
|
||||
console.log('%c' + args[0], 'color:green');
|
||||
} else if (LogRegExps.errorRegExp.test(args[0])) {
|
||||
console.log('%c' + args[0], 'color:red');
|
||||
}else if (LogRegExps.transferRegExp.test(args[0])) {
|
||||
} else if (LogRegExps.transferRegExp.test(args[0])) {
|
||||
console.log('%c' + args[0], 'color:orange');
|
||||
} else {
|
||||
console.log(...args);
|
||||
|
||||
@@ -4,8 +4,7 @@ import { LogService } from 'uhk-common';
|
||||
import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
|
||||
import { ElectronLogService } from './services/electron-log.service';
|
||||
import { ElectronErrorHandlerService } from './services/electron-error-handler.service';
|
||||
import { routing } from '../../';
|
||||
import { MainAppComponent } from '../../';
|
||||
import { MainAppComponent, routing } from '../../';
|
||||
import { IpcUhkRenderer } from './services/ipc-uhk-renderer';
|
||||
import { IpcCommonRenderer } from '../app/services/ipc-common-renderer';
|
||||
import { DataStorageRepositoryService } from '../app/services/datastorage-repository.service';
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
@import './styles/tooltip';
|
||||
@import './styles/uhk-icons/uhk-icon';
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.uhk-icon {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
@@ -30,6 +35,7 @@
|
||||
.main-page-content {
|
||||
margin-left: 250px;
|
||||
overflow-x: hidden;
|
||||
height: 99%;
|
||||
}
|
||||
|
||||
.select2 {
|
||||
@@ -69,3 +75,18 @@ ul.btn-list {
|
||||
.h1, .h2, .h3, h1, h2, h3 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
a.disabled {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
27
packages/usb/package-lock.json
generated
27
packages/usb/package-lock.json
generated
@@ -1,14 +1,11 @@
|
||||
{
|
||||
"name": "agent-usb",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "8.0.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.28.tgz",
|
||||
"integrity": "sha512-HupkFXEv3O3KSzcr3Ylfajg0kaerBg1DyaZzRBBQfrU3NN1mTBRE7sCveqHwXLS5Yrjvww8qFzkzYQQakG9FuQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-HupkFXEv3O3KSzcr3Ylfajg0kaerBg1DyaZzRBBQfrU3NN1mTBRE7sCveqHwXLS5Yrjvww8qFzkzYQQakG9FuQ=="
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
@@ -227,6 +224,11 @@
|
||||
"once": "1.4.0"
|
||||
}
|
||||
},
|
||||
"es6-object-assign": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
|
||||
"integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
@@ -623,6 +625,16 @@
|
||||
"rechoir": "0.6.2"
|
||||
}
|
||||
},
|
||||
"shx": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/shx/-/shx-0.2.2.tgz",
|
||||
"integrity": "sha1-CjBNAgsO3xMGrYFXDoDwNG31ijk=",
|
||||
"requires": {
|
||||
"es6-object-assign": "1.1.0",
|
||||
"minimist": "1.2.0",
|
||||
"shelljs": "0.7.8"
|
||||
}
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
@@ -728,6 +740,11 @@
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz",
|
||||
"integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w=="
|
||||
},
|
||||
"unbzip2-stream": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz",
|
||||
|
||||
10
scripts/copy-blhost.js
Normal file
10
scripts/copy-blhost.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const fse = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
fse.copy(
|
||||
path.join(__dirname, '../packages/usb/blhost'),
|
||||
path.join(__dirname, '../tmp/packages/blhost'),
|
||||
{
|
||||
overwrite:true,
|
||||
recursive:true
|
||||
});
|
||||
48
scripts/download-firmware.js
Normal file
48
scripts/download-firmware.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const request = require('request');
|
||||
const decompress = require('decompress');
|
||||
const decompressTarbz = require('decompress-tarbz2');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const fse = require('fs-extra');
|
||||
|
||||
async function downloadFirmware(version) {
|
||||
const url = `https://github.com/UltimateHackingKeyboard/firmware/releases/download/${version}/uhk-firmware-${version}.tar.bz2`;
|
||||
const outputDir = path.join(__dirname, `../tmp`);
|
||||
const output = path.join(outputDir, `uhk-firmware-${version}.tar.bz2`);
|
||||
|
||||
if (!fs.existsSync(outputDir))
|
||||
fs.mkdirSync(outputDir);
|
||||
|
||||
await downloadFile(url, output);
|
||||
|
||||
return Promise.resolve(output);
|
||||
}
|
||||
|
||||
async function downloadFile(url, output) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`Start download ${url}`);
|
||||
|
||||
const r = request(url);
|
||||
r.on('end', () => {
|
||||
resolve(output);
|
||||
});
|
||||
r.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
r.pipe(fs.createWriteStream(output));
|
||||
})
|
||||
}
|
||||
|
||||
(async function main() {
|
||||
const agentJson = require('../packages/uhk-agent/src/package.json');
|
||||
|
||||
const extractedFirmwareDir = path.join(__dirname, '../tmp/packages/firmware');
|
||||
await fse.emptyDir(extractedFirmwareDir);
|
||||
|
||||
// Download the firmware and add as extra resources
|
||||
const firmwarePath = await downloadFirmware(agentJson.firmwareVersion);
|
||||
await decompress(firmwarePath, extractedFirmwareDir, {plugins: [decompressTarbz()]});
|
||||
|
||||
await fse.remove(firmwarePath);
|
||||
})();
|
||||
@@ -3,6 +3,8 @@ const jsonfile = require('jsonfile');
|
||||
const exec = require('child_process').execSync;
|
||||
|
||||
const TEST_BUILD = process.env.TEST_BUILD;// set true if you would like to test on your local machine
|
||||
// set true if running on your dev mac machine where yarn is installed or not need to install
|
||||
const RUNNING_IN_DEV_MODE = process.env.RUNNING_IN_DEV_MODE === 'true';
|
||||
const DIR = process.env.DIR;
|
||||
|
||||
// electron-builder security override.
|
||||
@@ -40,11 +42,13 @@ if (!isReleaseCommit) {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
if (process.platform === 'darwin' && !RUNNING_IN_DEV_MODE) {
|
||||
exec('brew install yarn --without-node');
|
||||
}
|
||||
|
||||
exec("yarn add electron-builder");
|
||||
if (!RUNNING_IN_DEV_MODE) {
|
||||
exec("yarn add electron-builder");
|
||||
}
|
||||
|
||||
const path = require('path');
|
||||
const builder = require("electron-builder");
|
||||
@@ -83,11 +87,13 @@ if (process.platform === 'darwin') {
|
||||
//require('./setup-macos-keychain').registerKeyChain();
|
||||
}
|
||||
|
||||
let version = '';
|
||||
if (TEST_BUILD || gitTag) {
|
||||
const jsonVersion = require('../package.json').version;
|
||||
version = gitTag;
|
||||
updateVersionNumberIn2rndPackageJson(jsonVersion);
|
||||
const rootJson = require('../package.json');
|
||||
update2ndPackageJson(rootJson);
|
||||
|
||||
// Add firmware and blhost to extra resources
|
||||
const extractedFirmwareDir = path.join(__dirname, '../tmp/packages');
|
||||
extraResources.push({from: extractedFirmwareDir, to: 'packages/'});
|
||||
|
||||
builder.build({
|
||||
dir: DIR,
|
||||
@@ -98,7 +104,7 @@ if (TEST_BUILD || gitTag) {
|
||||
author: {
|
||||
name: 'Ultimate Gadget Laboratories'
|
||||
},
|
||||
version: jsonVersion
|
||||
version: rootJson.version
|
||||
},
|
||||
config: {
|
||||
directories: {
|
||||
@@ -107,7 +113,8 @@ if (TEST_BUILD || gitTag) {
|
||||
appId: 'com.ultimategadgetlabs.uhk.agent',
|
||||
productName: 'UHK Agent',
|
||||
mac: {
|
||||
category: 'public.app-category.utilities'
|
||||
category: 'public.app-category.utilities',
|
||||
extraResources
|
||||
},
|
||||
win: {
|
||||
extraResources
|
||||
@@ -133,15 +140,13 @@ if (TEST_BUILD || gitTag) {
|
||||
else {
|
||||
console.log('No git tag');
|
||||
// TODO: Need it?
|
||||
version = sha.substr(0, 8);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function updateVersionNumberIn2rndPackageJson(version) {
|
||||
function update2ndPackageJson(rootJson) {
|
||||
const jsonPath = path.join(__dirname, '../packages/uhk-agent/dist/package.json');
|
||||
const json = require(jsonPath);
|
||||
|
||||
json.version = version;
|
||||
|
||||
json.version = rootJson.version;
|
||||
jsonfile.writeFileSync(jsonPath, json, {spaces: 2})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user