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:
Róbert Kiss
2017-11-27 22:12:43 +01:00
committed by László Monda
parent f608791a09
commit 297fd3be79
52 changed files with 3914 additions and 894 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ dist
*.iml
*.sublime-project
*.sublime-workspace
tmp/

413
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
declare module 'decompress';

View File

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

View File

@@ -0,0 +1,7 @@
import { SynchrounousResult } from 'tmp';
export interface TmpFirmware {
rightFirmwarePath: string;
leftFirmwarePath: string;
tmpDirectory: SynchrounousResult;
}

View File

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

View File

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

View File

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

View File

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

View 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();
});
});
}

View File

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

View File

@@ -3,3 +3,4 @@ export * from './notification';
export * from './ipc-response';
export * from './app-start-info';
export * from './configuration-reply';
export * from './version-information';

View File

@@ -0,0 +1,7 @@
export interface VersionInformation {
version: string;
dataModelVersion: string;
usbProtocolVersion: string;
slaveProtocolVersion: string;
firmwareVersion: string;
}

View File

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

View File

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

View File

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

View File

@@ -1,2 +1,5 @@
export * from './constants';
export * from './uhk-blhost';
export * from './uhk-hid-device';
export * from './uhk-operations';
export * from './util';

View 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;
}
}

View File

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

View 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}`);
}
}

View 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}`);
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,40 @@
<h1>
<i class="fa fa-sliders"></i>
<span>Firmware</span>
</h1>
<p>
Coming soon ...
</p>
<div class="full-height">
<div class="flex-container">
<div>
<h1>
<i class="fa fa-sliders"></i>
<span>Firmware</span>
</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>

View File

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

View File

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

View File

@@ -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,17 +40,22 @@
<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">
<i class="fa fa-plus"></i>
</a>
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'keymap')"></i>
<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>
</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."
data-toggle="tooltip" data-placement="bottom"></i>
<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>
</ul>
@@ -55,15 +63,18 @@
<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">
<i class="fa fa-plus"></i>
</a>
<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>
</div>
<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>

View File

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

View File

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

View File

@@ -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: '$ ';
}
}
}

View 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> = [];
}

View 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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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';

View File

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

View File

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

View 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);
})();

View File

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