From 9b2f1b13c3f2dfadedb2e06e1e2f71b3b0c25422 Mon Sep 17 00:00:00 2001
From: Godpp <tzj0429@163.com>
Date: Mon, 17 Feb 2020 22:01:39 +0800
Subject: [PATCH] 123

---
 Pipfile.lock                              |  436 +++++++++
 widgets/diagnostic_trouble_code_widget.py |  455 +++++++++
 .gitignore                                |  148 +++
 config.ini                                |    3 
 widgets/flash_bootloader_widget.py        |  504 ++++++++++
 abc.ico                                   |    0 
 Pipfile                                   |   21 
 build.sh                                  |    5 
 README.md                                 |   26 
 widgets/data_identifier_widget.py         |  794 ++++++++++++++++
 ControlCAN.dll                            |    0 
 widgets/main_widget.py                    |   56 +
 build.bat                                 |    3 
 main.py                                   |   24 
 uds/client.py                             |  345 +++++++
 15 files changed, 2,820 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0ccd249
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,148 @@
+# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
+
+# Created by https://www.gitignore.io/api/visualstudiocode,python
+# Edit at https://www.gitignore.io/?templates=visualstudiocode,python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+
+# End of https://www.gitignore.io/api/visualstudiocode,python
+
+# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
+.vscode/*
+files/*
+.idea/*
diff --git a/ControlCAN.dll b/ControlCAN.dll
new file mode 100644
index 0000000..06137ec
--- /dev/null
+++ b/ControlCAN.dll
Binary files differ
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000..e6229fe
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,21 @@
+[[source]]
+name = "pypi"
+url = "https://mirrors.aliyun.com/pypi/simple/"
+verify_ssl = false
+
+[dev-packages]
+autopep8 = "==1.4.4"
+pylint = "==2.3.1"
+
+[packages]
+aioisotp = "==0.1.1"
+udsoncan = "==1.8"
+pyqt5 = "==5.12.2"
+asyncqt = "==0.7.0"
+autopep8 = "==1.4.4"
+pylint = "==2.3.1"
+pyinstaller = "==3.5"
+pywin32-ctypes ="==0.2.0"
+
+[requires]
+python_version = "3.7"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000..e6e02e6
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,436 @@
+{
+    "_meta": {
+        "hash": {
+            "sha256": "52208087d2a67939bdd327679176e1be6ac48f123bc907ac01736becd16fe1f5"
+        },
+        "pipfile-spec": 6,
+        "requires": {
+            "python_version": "3.7"
+        },
+        "sources": [
+            {
+                "name": "pypi",
+                "url": "https://mirrors.aliyun.com/pypi/simple/",
+                "verify_ssl": false
+            }
+        ]
+    },
+    "default": {
+        "aenum": {
+            "hashes": [
+                "sha256:284ddb976413d97239a932d7e5202ba58d66e5dbd81531bf3033ebb36ec30b23",
+                "sha256:a4334cabf47c167d44ab5a6198837b80deec5d5bad1b5cf70c966c3a330260e8",
+                "sha256:d2bb6ea7586aaae889d3a5c332eafa851eeffe6e7068807c79b6c86c4326b938"
+            ],
+            "version": "==2.2.3"
+        },
+        "aioisotp": {
+            "hashes": [
+                "sha256:27bcffe39835e937e7be2f3374e613399d2c95631b324d292caf1f5a18f829bd",
+                "sha256:f998284978a7a41a5b527fdcf1555fe7996fd98e80b48dbe95c005df1ddf1b4a"
+            ],
+            "index": "pypi",
+            "version": "==0.1.1"
+        },
+        "altgraph": {
+            "hashes": [
+                "sha256:1f05a47122542f97028caf78775a095fbe6a2699b5089de8477eb583167d69aa",
+                "sha256:c623e5f3408ca61d4016f23a681b9adb100802ca3e3da5e718915a9e4052cebe"
+            ],
+            "version": "==0.17"
+        },
+        "astroid": {
+            "hashes": [
+                "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a",
+                "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"
+            ],
+            "version": "==2.3.3"
+        },
+        "asyncqt": {
+            "hashes": [
+                "sha256:8b1507c968c85cf0b7eee5d2a887162d38af15fb8c5b1ec25beed6025d7383ac",
+                "sha256:e0f51a0c27d7276b50ef2a4539e0d56bbcf5e837854caba40b41741dbe36ffb3"
+            ],
+            "index": "pypi",
+            "version": "==0.7.0"
+        },
+        "autopep8": {
+            "hashes": [
+                "sha256:4d8eec30cc81bc5617dbf1218201d770dc35629363547f17577c61683ccfb3ee"
+            ],
+            "index": "pypi",
+            "version": "==1.4.4"
+        },
+        "colorama": {
+            "hashes": [
+                "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
+                "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
+            ],
+            "markers": "sys_platform == 'win32'",
+            "version": "==0.4.3"
+        },
+        "future": {
+            "hashes": [
+                "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
+            ],
+            "version": "==0.18.2"
+        },
+        "isort": {
+            "hashes": [
+                "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
+                "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
+            ],
+            "version": "==4.3.21"
+        },
+        "lazy-object-proxy": {
+            "hashes": [
+                "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d",
+                "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449",
+                "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08",
+                "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a",
+                "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50",
+                "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd",
+                "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239",
+                "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb",
+                "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea",
+                "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e",
+                "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156",
+                "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142",
+                "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442",
+                "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62",
+                "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db",
+                "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531",
+                "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383",
+                "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a",
+                "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357",
+                "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
+                "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
+            ],
+            "version": "==1.4.3"
+        },
+        "mccabe": {
+            "hashes": [
+                "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+                "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+            ],
+            "version": "==0.6.1"
+        },
+        "packaging": {
+            "hashes": [
+                "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73",
+                "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"
+            ],
+            "version": "==20.1"
+        },
+        "pefile": {
+            "hashes": [
+                "sha256:a5d6e8305c6b210849b47a6174ddf9c452b2888340b8177874b862ba6c207645"
+            ],
+            "version": "==2019.4.18"
+        },
+        "pycodestyle": {
+            "hashes": [
+                "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
+                "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
+            ],
+            "version": "==2.5.0"
+        },
+        "pyinstaller": {
+            "hashes": [
+                "sha256:ee7504022d1332a3324250faf2135ea56ac71fdb6309cff8cd235de26b1d0a96"
+            ],
+            "index": "pypi",
+            "version": "==3.5"
+        },
+        "pylint": {
+            "hashes": [
+                "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09",
+                "sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1"
+            ],
+            "index": "pypi",
+            "version": "==2.3.1"
+        },
+        "pyparsing": {
+            "hashes": [
+                "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
+                "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
+            ],
+            "version": "==2.4.6"
+        },
+        "pyqt5": {
+            "hashes": [
+                "sha256:06cb9a1ea1289ca2b2d81f9a2da44a05029d5b4d36fa47b6a0c0b9027ff03fef",
+                "sha256:44b263f395045eb5bba34f4df2d269e07b1e073fd359645b022943dd1accadfd",
+                "sha256:bb4ec5583baa18f741ec6c229489dc1910290d4ce4d6756a2ea062f7bf6456e6",
+                "sha256:c168a8883bbe7877809c3239c5dcfb9e8de5fe7e8e828c8add7e4f77cc8fc02a"
+            ],
+            "index": "pypi",
+            "version": "==5.12.2"
+        },
+        "pyqt5-sip": {
+            "hashes": [
+                "sha256:1115728644bbadcde5fc8a16e7918bd31915a42dd6fb36b10d4afb78c582753e",
+                "sha256:1f4289276d355b6521dc2cc956189315da6f13adfb6bbab8f25ebd15e3bce1d4",
+                "sha256:288c6dc18a8d6a20981c07b715b5695d9b66880778565f3792bc6e38f14f20fb",
+                "sha256:3f665376d9e52faa9855c3736a66ce6d825f85c86d7774d3c393f09da23f4f86",
+                "sha256:6b4860c4305980db509415d0af802f111d15f92016c9422eb753bc8883463456",
+                "sha256:7ffa39763097f64de129cf5cc770a651c3f65d2466b4fe05bef2bd2efbaa38e6",
+                "sha256:8a18e6f45d482ddfe381789979d09ee13aa6450caa3a0476503891bccb3ac709",
+                "sha256:8da842d3d7bf8931d1093105fb92702276b6dbb7e801abbaaa869405d616171a",
+                "sha256:b42021229424aa44e99b3b49520b799fd64ff6ae8b53f79f903bbd85719a28e4",
+                "sha256:b5b4906445fe980aee76f20400116b6904bf5f30d0767489c13370e42a764020",
+                "sha256:c1e730a9eb2ec3869ed5d81b0f99f6e2460fb4d77750444c0ec183b771d798f7",
+                "sha256:cbeeae6b45234a1654657f79943f8bccd3d14b4e7496746c62cf6fbce69442c7",
+                "sha256:d46b0f8effc554de52a1466b1bd80e5cb4bce635a75ac4e7ad6247c965dec5b9",
+                "sha256:e28c3abc9b62a1b7e796891648b9f14f8167b31c8e7990fae79654777252bb4d",
+                "sha256:e6078f5ee7d31c102910d0c277a110e1c2a20a3fc88cd017a39e170120586d3f",
+                "sha256:ee1a12f09d5af2304273bfd2f6b43835c1467d5ed501a6c95f5405637fa7750a",
+                "sha256:f314f31f5fd39b06897f013f425137e511d45967150eb4e424a363d8138521c6"
+            ],
+            "version": "==12.7.1"
+        },
+        "python-can": {
+            "hashes": [
+                "sha256:5fefb5c1e7e7f07faefc02c6eac79f9b58376f007048a04d8e7f325d48ec6b2e"
+            ],
+            "version": "==3.3.2"
+        },
+        "pywin32": {
+            "hashes": [
+                "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be",
+                "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511",
+                "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0",
+                "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507",
+                "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116",
+                "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e",
+                "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc",
+                "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2",
+                "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4",
+                "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295",
+                "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c",
+                "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"
+            ],
+            "index": "pypi",
+            "version": "==227"
+        },
+        "pywin32-ctypes": {
+            "hashes": [
+                "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942",
+                "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"
+            ],
+            "version": "==0.2.0"
+        },
+        "sip": {
+            "hashes": [
+                "sha256:066654acad02685cbdc84c3741e56b45845505426e9fc3c193fd3fe09afc0738",
+                "sha256:1b4c78e57b5876b9848c7634dbde5f9a1eff21cc13d0c589d461b5d4370b8b09",
+                "sha256:3b600dd1c8293bb7cf002a20dbbc736010c6a5e161241d73b16685744ffe57c2",
+                "sha256:42f7d43379e8f9bb50997870e94bdd691eadfe61ca2dfd679f821144afb50f24",
+                "sha256:4fe6f7e1a1f1bf38cbc6cbc5f0888934d8fc101d1478b0514477a7b44f3d41c0",
+                "sha256:62df2afa4c872bcac15746d8082de0ff14cfff6a487d5e25c6568bff5298cb88",
+                "sha256:9193914b685d02cd9a3d562d0132ab2297bc7b518a591a4ef39a1ca59da31fdd",
+                "sha256:9f331f61250df2427a92acabf18dfb2383e6ca74407fc5ff05c1cb037b03b62f",
+                "sha256:b2fda76ecb78df121fc165104050890c23ac41fa91e2a7d1b9a37aa22b212550",
+                "sha256:ba4a8a75bde2e58ee5bf28cf7c1aa3e90d2f51e9386ab812b0220786d684b340",
+                "sha256:bc9e061b16e6b3836621d4f6e6e8af0db3f2715643d54b975359ddb754896439",
+                "sha256:d08f9566f9a869d390f0c70e8e514eb4f650c56e5f1bd894fbceb793b598b9f0",
+                "sha256:de370fc4048684123c78f2f6e261d9ac53b51dc005f727f362e4e671322cdbd5",
+                "sha256:e896200e4a05bc8bf2ad5da3a9f733ca56f313bf0c6366a71677b39fd6a2ec2f",
+                "sha256:eeba96ca1d1bbf8de9b262df500176a689bdfc338d0f418eca7ff432731839a4",
+                "sha256:f287bb196e3453365032bc0931b61419e0a61631729f7ffa2df1aba5c49514d7",
+                "sha256:fbf8f71a26a94f7e32eb780bea0e55856dbb99dbcf33a99529565ed5cd060259"
+            ],
+            "index": "pypi",
+            "version": "==5.1.1"
+        },
+        "six": {
+            "hashes": [
+                "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
+                "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
+            ],
+            "version": "==1.14.0"
+        },
+        "toml": {
+            "hashes": [
+                "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
+                "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
+            ],
+            "version": "==0.10.0"
+        },
+        "typed-ast": {
+            "hashes": [
+                "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
+                "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
+                "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
+                "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
+                "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
+                "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
+                "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
+                "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
+                "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
+                "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
+                "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
+                "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
+                "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
+                "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
+                "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
+                "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
+                "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
+                "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
+                "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
+                "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
+                "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
+            ],
+            "markers": "implementation_name == 'cpython' and python_version < '3.8'",
+            "version": "==1.4.1"
+        },
+        "udsoncan": {
+            "hashes": [
+                "sha256:beb34bf11c229131c2e87fd0e0263b91787913e79d8462408e8cb04666351e8b",
+                "sha256:c71195d3a8fa10f0b17fb13d705c0dd2a37c1100855d95e6c3fede3e8fef3c3b"
+            ],
+            "index": "pypi",
+            "version": "==1.8"
+        },
+        "windows-curses": {
+            "hashes": [
+                "sha256:261fde5680d1ce4ce116908996b9a3cfb0ffb03ea68d42240f62b56a9fa6af2c",
+                "sha256:66034dc9a705d87308cc9ea90836f4ee60008a1d5e2c1d34ace627f60268158b",
+                "sha256:669caad3ae16faf2d201d7ab3b8af418a2fd074d8a39d60ca26f3acb34b6afe5",
+                "sha256:73bd3eebccfda55330783f165151de115bfa238d1332f0b2e224b550d6187840",
+                "sha256:89a6d973f88cfe49b41ea80164dcbec209d296e0cec34a02002578b0bf464a64",
+                "sha256:8ba7c000d7ffa5452bbd0966b96e69261e4f117ebe510aeb8771a9650197b7f0",
+                "sha256:97084c6b37b1534f6a28a514d521dfae402f77dcbad42b14ee32e8d5bdc13648",
+                "sha256:9e474a181f96d60429a4766145628264e60b72e7715876f9135aeb2e842f9433",
+                "sha256:cfe64c30807c146ef8d094412f90f2a2c81ad6aefff3ebfe8e37aabe2f801303",
+                "sha256:ff8c67f74b88944d99fa9d22971c05c335bc74f149120f0a69340c2c3a595497"
+            ],
+            "version": "==2.1.0"
+        },
+        "wrapt": {
+            "hashes": [
+                "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"
+            ],
+            "version": "==1.11.2"
+        }
+    },
+    "develop": {
+        "astroid": {
+            "hashes": [
+                "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a",
+                "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"
+            ],
+            "version": "==2.3.3"
+        },
+        "autopep8": {
+            "hashes": [
+                "sha256:4d8eec30cc81bc5617dbf1218201d770dc35629363547f17577c61683ccfb3ee"
+            ],
+            "index": "pypi",
+            "version": "==1.4.4"
+        },
+        "colorama": {
+            "hashes": [
+                "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
+                "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
+            ],
+            "markers": "sys_platform == 'win32'",
+            "version": "==0.4.3"
+        },
+        "isort": {
+            "hashes": [
+                "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
+                "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
+            ],
+            "version": "==4.3.21"
+        },
+        "lazy-object-proxy": {
+            "hashes": [
+                "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d",
+                "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449",
+                "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08",
+                "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a",
+                "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50",
+                "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd",
+                "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239",
+                "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb",
+                "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea",
+                "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e",
+                "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156",
+                "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142",
+                "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442",
+                "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62",
+                "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db",
+                "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531",
+                "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383",
+                "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a",
+                "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357",
+                "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
+                "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
+            ],
+            "version": "==1.4.3"
+        },
+        "mccabe": {
+            "hashes": [
+                "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+                "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+            ],
+            "version": "==0.6.1"
+        },
+        "pycodestyle": {
+            "hashes": [
+                "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
+                "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
+            ],
+            "version": "==2.5.0"
+        },
+        "pylint": {
+            "hashes": [
+                "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09",
+                "sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1"
+            ],
+            "index": "pypi",
+            "version": "==2.3.1"
+        },
+        "six": {
+            "hashes": [
+                "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
+                "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
+            ],
+            "version": "==1.14.0"
+        },
+        "typed-ast": {
+            "hashes": [
+                "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
+                "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
+                "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
+                "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
+                "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
+                "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
+                "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
+                "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
+                "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
+                "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
+                "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
+                "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
+                "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
+                "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
+                "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
+                "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
+                "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
+                "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
+                "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
+                "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
+                "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
+            ],
+            "markers": "implementation_name == 'cpython' and python_version < '3.8'",
+            "version": "==1.4.1"
+        },
+        "wrapt": {
+            "hashes": [
+                "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"
+            ],
+            "version": "==1.11.2"
+        }
+    }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..92e6fe5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+# 修改的第三方库源码
+```py
+# 注释 udsoncan.services.ReadDataByIdentifier 107-108行
+if len(response.data) < offset+len(codec):
+    raise InvalidResponseException(response, "Value for data identifier 0x%04x was incomplete according to definition in configuration" % did)
+# logger.error 添加异常抛出 can.interfaces.canalystii
+# 105行
+logger.error("VCI_OpenDevice Error")
+raise Exception('VCI_OpenDevice Error')
+# 110行
+logger.error("VCI_InitCAN Error")
+self.shutdown()
+raise Exception('VCI_InitCAN Error')
+# 116行
+logger.error("VCI_StartCAN Error")
+self.shutdown()
+raise Exception('VCI_StartCAN Error')
+```
+
+# 打包为exe后,复制或创建config.ini到exe目录
+# config.ini示例
+```ini
+[CAN_ID]
+tx_id = 0x692
+rx_id = 0x693
+```
diff --git a/abc.ico b/abc.ico
new file mode 100644
index 0000000..404a2c7
--- /dev/null
+++ b/abc.ico
Binary files differ
diff --git a/build.bat b/build.bat
new file mode 100644
index 0000000..684a632
--- /dev/null
+++ b/build.bat
@@ -0,0 +1,3 @@
+REM Before running this, make sure all PIP dependencies are installed.
+
+pyinstaller --clean -i abc.ico -F -w --add-binary "ControlCAN.dll;." main.py
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..8e819f8
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,5 @@
+#/bin/bash
+
+# Before running this, make sure all PIP dependencies are installed.
+
+pyinstaller --clean -F -w --add-binary "ControlCAN.dll:." main.py
diff --git a/config.ini b/config.ini
new file mode 100644
index 0000000..3d9ef9e
--- /dev/null
+++ b/config.ini
@@ -0,0 +1,3 @@
+[CAN_ID]
+tx_id = 0x692
+rx_id = 0x693
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..1ba63cb
--- /dev/null
+++ b/main.py
@@ -0,0 +1,24 @@
+import sys
+import asyncio
+from PyQt5.QtWidgets import QApplication
+from asyncqt import QEventLoop
+from widgets.flash_bootloader_widget import FlashBootloaderWidget
+# from widgets.data_identifier_widget import DataIdentifierWidget
+# from widgets.diagnostic_trouble_code_widget import DiagnosticTroubleCodeWidget
+# from widgets.main_widget import MainWidget
+
+if __name__ == '__main__':
+
+    app = QApplication(sys.argv)
+    loop = QEventLoop(app)
+    asyncio.set_event_loop(loop)
+
+    # w = MainWidget()
+    w = FlashBootloaderWidget()
+    # w = DataIdentifierWidget()
+    # w = DiagnosticTroubleCodeWidget()
+    w.show()
+
+    with loop:
+        sys.exit(loop.run_forever())
+        #Successfully installed altgraph-0.17 future-0.18.2 pefile-2019.4.18 pyinstaller-3.5 pywin32-ctypes-0.2.0
diff --git a/uds/client.py b/uds/client.py
new file mode 100644
index 0000000..1b72f78
--- /dev/null
+++ b/uds/client.py
@@ -0,0 +1,345 @@
+import asyncio
+
+from udsoncan import Response, Request, Dtc, MemoryLocation, services
+from udsoncan.exceptions import NegativeResponseException, UnexpectedResponseException, ConfigError
+from udsoncan.configs import default_client_config
+
+
+class Client(object):
+
+    def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, config=default_client_config):
+        self.reader = reader
+        self.writer = writer
+        self.config = dict(config)
+
+    async def send_request(self, request: Request, suppress_positive_response=None, timeout=None):
+        if timeout is None:
+            timeout = self.config['request_timeout']
+
+        payload = request.get_payload(suppress_positive_response)
+        self.writer.write(payload)
+
+        if suppress_positive_response is None:
+            payload = await asyncio.wait_for(self.reader.read(4095), timeout)
+            response = Response.from_payload(payload)
+
+            if not response.positive:
+                raise NegativeResponseException(response)
+
+            return response
+
+    async def change_session(self, newsession, suppress_positive_response=None, timeout=None):
+        request = services.DiagnosticSessionControl.make_request(newsession)
+
+        response = await self.send_request(request, suppress_positive_response, timeout)
+
+        if response is None:
+            return
+
+        services.DiagnosticSessionControl.interpret_response(response)
+
+        if newsession != response.service_data.session_echo:
+            raise UnexpectedResponseException(response, 'Response subfunction received from server (0x%02x) does not match the requested subfunction (0x%02x)' % (
+                response.service_data.session_echo, newsession))
+
+        return response
+
+    async def request_seed(self, level):
+        request = services.SecurityAccess.make_request(
+            level, mode=services.SecurityAccess.Mode.RequestSeed)
+
+        response = await self.send_request(request)
+
+        if response is None:
+            return
+
+        services.SecurityAccess.interpret_response(
+            response, mode=services.SecurityAccess.Mode.RequestSeed)
+
+        expected_level = services.SecurityAccess.normalize_level(
+            mode=services.SecurityAccess.Mode.RequestSeed, level=level)
+        received_level = response.service_data.security_level_echo
+
+        if expected_level != received_level:
+            raise UnexpectedResponseException(
+                response, 'Response subfunction received from server (0x%02x) does not match the requested subfunction (0x%02x)' % (received_level, expected_level))
+
+        return response
+
+    async def send_key(self, level, key):
+        request = services.SecurityAccess.make_request(
+            level, mode=services.SecurityAccess.Mode.SendKey, key=key)
+
+        response = await self.send_request(request)
+
+        if response is None:
+            return
+
+        services.SecurityAccess.interpret_response(
+            response, mode=services.SecurityAccess.Mode.SendKey)
+
+        expected_level = services.SecurityAccess.normalize_level(
+            mode=services.SecurityAccess.Mode.SendKey, level=level)
+        received_level = response.service_data.security_level_echo
+
+        if expected_level != received_level:
+            raise UnexpectedResponseException(
+                response, 'Response subfunction received from server (0x%02x) does not match the requested subfunction (0x%02x)' % (received_level, expected_level))
+
+        return response
+
+    async def tester_present(self, suppress_positive_response=None, timeout=None):
+        request = services.TesterPresent.make_request()
+
+        response = await self.send_request(request, suppress_positive_response, timeout)
+
+        if response is None:
+            return
+
+        services.TesterPresent.interpret_response(response)
+
+        if request.subfunction != response.service_data.subfunction_echo:
+            raise UnexpectedResponseException(response, 'Response subfunction received from server (0x%02x) does not match the requested subfunction (0x%02x)' % (
+                response.service_data.subfunction_echo, request.subfunction))
+
+        return response
+
+    async def read_data_by_identifier_first(self, didlist):
+        didlist = services.ReadDataByIdentifier.validate_didlist_input(didlist)
+        response = await self.read_data_by_identifier(didlist)
+        values = response.service_data.values
+        if len(values) > 0 and len(didlist) > 0:
+            return values[didlist[0]]
+
+    async def read_data_by_identifier(self, didlist):
+        didlist = services.ReadDataByIdentifier.validate_didlist_input(didlist)
+
+        request = services.ReadDataByIdentifier.make_request(
+            didlist=didlist, didconfig=self.config['data_identifiers'])
+
+        if 'data_identifiers' not in self.config or not isinstance(self.config['data_identifiers'], dict):
+            raise AttributeError(
+                'Configuration does not contains a valid data identifier description.')
+
+        response = await self.send_request(request)
+
+        if response is None:
+            return
+
+        params = {
+            'didlist': didlist,
+            'didconfig': self.config['data_identifiers'],
+            'tolerate_zero_padding': self.config['tolerate_zero_padding']
+        }
+
+        try:
+            services.ReadDataByIdentifier.interpret_response(
+                response, **params)
+        except ConfigError as e:
+            if e.key in didlist:
+                raise
+            else:
+                raise UnexpectedResponseException(
+                    response, "Server returned values for data identifier 0x%04x that was not requested and no Codec was defined for it. Parsing must be stopped." % (e.key))
+
+        set_request_didlist = set(didlist)
+        set_response_didlist = set(response.service_data.values.keys())
+        extra_did = set_response_didlist - set_request_didlist
+        missing_did = set_request_didlist - set_response_didlist
+
+        if len(extra_did) > 0:
+            raise UnexpectedResponseException(
+                response, "Server returned values for %d data identifier that were not requested. Dids are : %s" % (len(extra_did), extra_did))
+
+        if len(missing_did) > 0:
+            raise UnexpectedResponseException(
+                response, "%d data identifier values are missing from server response. Dids are : %s" % (len(missing_did), missing_did))
+
+        return response
+
+    async def write_data_by_identifier(self, did, value):
+        request = services.WriteDataByIdentifier.make_request(
+            did, value, didconfig=self.config['data_identifiers'])
+
+        response = await self.send_request(request)
+
+        if response is None:
+            return
+
+        services.WriteDataByIdentifier.interpret_response(response)
+
+        if response.service_data.did_echo != did:
+            raise UnexpectedResponseException(response, 'Server returned a response for data identifier 0x%04x while client requested for did 0x%04x' % (
+                response.service_data.did_echo, did))
+
+        return response
+
+    async def ecu_reset(self, reset_type, suppress_positive_response=None, timeout=None):
+        request = services.ECUReset.make_request(reset_type)
+
+        response = await self.send_request(request, suppress_positive_response, timeout)
+
+        if response is None:
+            return
+
+        services.ECUReset.interpret_response(response)
+
+        if response.service_data.reset_type_echo != reset_type:
+            raise UnexpectedResponseException(response, 'Response subfunction received from server (0x%02x) does not match the requested subfunction (0x%02x)' % (
+                response.service_data.reset_type_echo, reset_type))
+
+        return response
+
+    async def clear_dtc(self, group=0xFFFFFF):
+        request = services.ClearDiagnosticInformation.make_request(group)
+
+        response = await self.send_request(request)
+
+        if response is None:
+            return
+
+        services.ClearDiagnosticInformation.interpret_response(response)
+
+        return response
+
+    async def start_routine(self, routine_id, data=None):
+        response = await self.routine_control(routine_id, services.RoutineControl.ControlType.startRoutine, data)
+        return response
+
+    async def stop_routine(self, routine_id, data=None):
+        response = await self.routine_control(routine_id, services.RoutineControl.ControlType.stopRoutine, data)
+        return response
+
+    async def routine_control(self, routine_id, control_type, data=None):
+        request = services.RoutineControl.make_request(
+            routine_id, control_type, data=data)
+
+        response = await self.send_request(request)
+
+        if response is None:
+            return
+
+        services.RoutineControl.interpret_response(response)
+
+        if control_type != response.service_data.control_type_echo:
+            raise UnexpectedResponseException(response, 'Control type of response (0x%02x) does not match request control type (0x%02x)' % (
+                response.service_data.control_type_echo, control_type))
+
+        if routine_id != response.service_data.routine_id_echo:
+            raise UnexpectedResponseException(response, 'Response received from server (ID = 0x%04x) does not match the requested routine ID (0x%04x)' % (
+                response.service_data.routine_id_echo, routine_id))
+
+        return response
+
+    async def communication_control(self, control_type, communication_type, suppress_positive_response=None, timeout=None):
+        request = services.CommunicationControl.make_request(
+            control_type, communication_type)
+
+        response = await self.send_request(request, suppress_positive_response, timeout)
+
+        if response is None:
+            return
+
+        services.CommunicationControl.interpret_response(response)
+
+        if control_type != response.service_data.control_type_echo:
+            raise UnexpectedResponseException(response, 'Control type of response (0x%02x) does not match request control type (0x%02x)' % (
+                response.service_data.control_type_echo, control_type))
+
+        return response
+
+    async def request_download(self, memory_location, dfi=None):
+        response = await self.request_upload_download(services.RequestDownload, memory_location, dfi)
+        return response
+
+    async def request_upload(self, memory_location, dfi=None):
+        response = await self.request_upload_download(services.RequestUpload, memory_location, dfi)
+        return response
+
+    async def request_upload_download(self, service_cls, memory_location, dfi=None):
+        dfi = service_cls.normalize_data_format_identifier(dfi)
+
+        if service_cls not in [services.RequestDownload, services.RequestUpload]:
+            raise ValueError(
+                'Service must either be RequestDownload or RequestUpload')
+
+        if not isinstance(memory_location, MemoryLocation):
+            raise ValueError(
+                'memory_location must be an instance of MemoryLocation')
+
+        if 'server_address_format' in self.config:
+            memory_location.set_format_if_none(
+                address_format=self.config['server_address_format'])
+
+        if 'server_memorysize_format' in self.config:
+            memory_location.set_format_if_none(
+                memorysize_format=self.config['server_memorysize_format'])
+
+        request = service_cls.make_request(
+            memory_location=memory_location, dfi=dfi)
+
+        response = await self.send_request(request)
+
+        if response is None:
+            return
+
+        service_cls.interpret_response(response)
+
+        return response
+
+    async def transfer_data(self, sequence_number, data=None):
+        request = services.TransferData.make_request(sequence_number, data)
+
+        response = await self.send_request(request)
+
+        if response is None:
+            return
+
+        services.TransferData.interpret_response(response)
+
+        if sequence_number != response.service_data.sequence_number_echo:
+            raise UnexpectedResponseException(response, 'Block sequence number of response (0x%02x) does not match request block sequence number (0x%02x)' % (
+                response.service_data.sequence_number_echo, sequence_number))
+
+        return response
+
+    async def request_transfer_exit(self, data=None):
+        request = services.RequestTransferExit.make_request(data)
+
+        response = await self.send_request(request)
+
+        if response is None:
+            return
+
+        services.RequestTransferExit.interpret_response(response)
+
+        return response
+
+    async def control_dtc_setting(self, setting_type, data=None, suppress_positive_response=None, timeout=None):
+        request = services.ControlDTCSetting.make_request(
+            setting_type, data=data)
+
+        response = await self.send_request(request, suppress_positive_response, timeout)
+
+        if response is None:
+            return
+
+        services.ControlDTCSetting.interpret_response(response)
+
+        if response.service_data.setting_type_echo != setting_type:
+            raise UnexpectedResponseException(response, 'Setting type of response (0x%02x) does not match request control type (0x%02x)' % (
+                response.service_data.setting_type_echo, setting_type))
+
+        return response
+
+    async def get_dtc_by_status_mask(self, status_mask):
+        pass
+
+    async def get_number_of_dtc_by_status_mask(self, status_mask):
+        pass
+
+    async def get_supported_dtc(self):
+        pass
+
+    async def read_dtc_information(self, subfunction, status_mask=None, severity_mask=None,  dtc=None, snapshot_record_number=None, extended_data_record_number=None, extended_data_size=None):
+        pass
diff --git a/widgets/data_identifier_widget.py b/widgets/data_identifier_widget.py
new file mode 100644
index 0000000..c21a183
--- /dev/null
+++ b/widgets/data_identifier_widget.py
@@ -0,0 +1,794 @@
+from PyQt5 import QtWidgets, QtCore, QtGui
+from can.interfaces.canalystii import CANalystIIBus, TIMING_DICT
+from can.interfaces.vector import VectorBus
+from can.bus import BusABC
+import asyncio
+import time
+import aioisotp
+from uds.client import Client
+from udsoncan import DidCodec
+from udsoncan import DataIdentifier
+from udsoncan.configs import default_client_config
+import struct
+import configparser
+
+
+class AsciiCodec(DidCodec):
+
+    def __init__(self, string_len=None):
+        if string_len is None:
+            raise ValueError(
+                "You must provide a length to the AsciiCodec")
+        self.string_len = string_len
+
+    def encode(self, string_ascii):
+        if len(string_ascii) > self.string_len:
+            string_ascii = string_ascii[0:self.string_len]
+
+        elif len(string_ascii) < self.string_len:
+            for i in range(self.string_len - len(string_ascii)):
+                string_ascii += ' '
+
+        return string_ascii.encode('ascii')
+
+    def decode(self, string_bin):
+        string_ascii = string_bin.decode('ascii')
+        if len(string_ascii) > self.string_len:
+            string_ascii = string_ascii[0:self.string_len]
+
+        return string_ascii.strip()
+
+    def __len__(self):
+        return self.string_len
+
+
+class PartNumberCodec(DidCodec):
+    def __init__(self, byte_len=None):
+        if byte_len is None:
+            raise ValueError(
+                "You must provide a length to the PartNumberCodec")
+        self.byte_len = byte_len
+
+    def encode(self, string_ascii):
+        if self.byte_len == 1:
+            data = struct.pack('>B', int(string_ascii))
+        elif self.byte_len == 2:
+            data = struct.pack('>H', int(string_ascii))
+        elif self.byte_len == 4:
+            data = struct.pack('>L', int(string_ascii))
+        elif self.byte_len == 8:
+            data = struct.pack('>Q', int(string_ascii))
+
+        if len(data) > self.byte_len:
+            data = data[0:self.byte_len]
+
+        return data
+
+    def decode(self, byte_data):
+        data = 0
+        for i in byte_data:
+            data = data << 8 | i
+        return str(data)
+
+    def __len__(self):
+        return self.byte_len
+
+
+class PartNumberVersionCodec(DidCodec):
+    def __init__(self, byte_len=None):
+        if byte_len is None:
+            raise ValueError(
+                "You must provide a length to the PartNumberCodec")
+        self.byte_len = byte_len
+
+    def encode(self, string_ascii):
+        if self.byte_len == 1:
+            data = struct.pack('>B', int(string_ascii))
+        elif self.byte_len == 2:
+            data = struct.pack('>H', int(string_ascii))
+        elif self.byte_len == 4:
+            data = struct.pack('>L', int(string_ascii))
+        elif self.byte_len == 8:
+            data = struct.pack('>Q', int(string_ascii))
+
+        if len(data) > self.byte_len:
+            data = data[0:self.byte_len]
+
+        return data
+
+    def decode(self, byte_data):
+        ret = ''
+        for i in byte_data:
+            ret += '%.1x' % i
+        return ret
+
+    def __len__(self):
+        return self.byte_len
+
+
+class BCDCodec(DidCodec):
+
+    def __init__(self, byte_len=None):
+        self.byte_len = byte_len
+
+    def encode(self, string_ascii):
+        string_ascii = string_ascii.strip()
+
+        if len(string_ascii) > self.byte_len * 2:
+            string_ascii = string_ascii[0:self.byte_len * 2]
+        elif len(string_ascii) < self.byte_len * 2:
+            for i in range(self.byte_len * 2 - len(string_ascii)):
+                string_ascii += '0'
+
+        data = bytearray()
+        for i in range(self.byte_len):
+            part = string_ascii[2 * i: 2 * i + 2]
+            data.append(int(part, 16))
+
+        return data
+
+    def decode(self, byte_data):
+        ret = ''
+        for i in byte_data:
+            ret += '%.2x' % i
+
+        return ret
+
+    def __len__(self):
+        return self.byte_len
+
+
+class DataIdentifierWidget(QtWidgets.QWidget):
+
+    def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags()):
+        super().__init__(parent=parent, flags=flags)
+        self.initUI()
+        self.client: Client = None
+        self.opened_flag = False
+        self.did_config = {
+            0xF190: AsciiCodec(17),
+            0xF1B1: AsciiCodec(9),
+            0xF1B2: AsciiCodec(16),
+
+            0xF1D0: PartNumberCodec(4),
+            0xF1D1: PartNumberVersionCodec(2),
+            0xF1D2: PartNumberCodec(4),
+            0xF1D3: PartNumberVersionCodec(2),
+            0xF0DF: PartNumberVersionCodec(1),
+
+            0xF187: AsciiCodec(21),
+            0xF195: AsciiCodec(16),
+            0xF198: BCDCodec(8),
+            0xF199: BCDCodec(4),
+        }
+
+        try:
+            config = configparser.ConfigParser()
+            config.read('config.ini')
+            self.tx_id = int(config['CAN_ID']['tx_id'], 16)
+            self.rx_id = int(config['CAN_ID']['rx_id'], 16)
+            self.append_text_to_log_display('read config.ini success')
+        except Exception as e:
+            self.append_text_to_log_display(
+                'read config.ini error, use 0x692 and 0x693 for default tx_id and rx_id', '#ff0000')
+            self.tx_id = 0x692
+            self.rx_id = 0x693
+
+    def initUI(self):
+
+        self.setWindowTitle('Data Identifier')
+        self.setFont(QtGui.QFont('Segoe UI'))
+
+        cb_interface_type = QtWidgets.QComboBox(self)
+        cb_interface_type.addItems(['vector', 'canalystii'])
+        cb_interface_type.setObjectName('cb_interface_type')
+
+        cb_baudrate = QtWidgets.QComboBox(self)
+        cb_baudrate.addItems([('%dK' % (i/1000)) for i in TIMING_DICT.keys()])
+        cb_baudrate.setCurrentIndex(14)
+        cb_baudrate.setObjectName('cb_baudrate')
+
+        cb_can_channel = QtWidgets.QComboBox(self)
+        cb_can_channel.addItems(['CH1/3', 'CH2/4'])
+        cb_can_channel.setObjectName('cb_can_channel')
+
+        btn_open = QtWidgets.QPushButton(self)
+        btn_open.setText('open')
+        btn_open.setObjectName('btn_open')
+        btn_close = QtWidgets.QPushButton(self)
+        btn_close.setText('close')
+        btn_close.setObjectName('btn_close')
+
+        hb0 = QtWidgets.QHBoxLayout()
+        hb0.addWidget(cb_interface_type)
+        hb0.addWidget(cb_baudrate)
+        hb0.addWidget(cb_can_channel)
+        hb0.addWidget(btn_open)
+        hb0.addWidget(btn_close)
+
+        # 0xF190
+        lb_0xf190 = QtWidgets.QLabel(self)
+        lb_0xf190.setText('Vehicle Identification Number')
+        le_0xf190 = QtWidgets.QLineEdit(self)
+        le_0xf190.setObjectName('le_0xf190')
+        btn_read_0xf190 = QtWidgets.QPushButton(self)
+        btn_read_0xf190.setText('read')
+        btn_read_0xf190.setObjectName('btn_read_0xf190')
+        btn_write_0xf190 = QtWidgets.QPushButton(self)
+        btn_write_0xf190.setText('write')
+        btn_write_0xf190.setObjectName('btn_write_0xf190')
+
+        # 0xF1B1
+        lb_0xf1b1 = QtWidgets.QLabel(self)
+        lb_0xf1b1.setText('Data Universal Numbering System')
+        le_0xf1b1 = QtWidgets.QLineEdit(self)
+        le_0xf1b1.setObjectName('le_0xf1b1')
+        btn_read_0xf1b1 = QtWidgets.QPushButton(self)
+        btn_read_0xf1b1.setText('read')
+        btn_read_0xf1b1.setObjectName('btn_read_0xf1b1')
+
+        # 0xF1B2
+        lb_0xf1b2 = QtWidgets.QLabel(self)
+        lb_0xf1b2.setText('Manufacturing Traceability Characters')
+        le_0xf1b2 = QtWidgets.QLineEdit(self)
+        le_0xf1b2.setObjectName('le_0xf1b2')
+        btn_read_0xf1b2 = QtWidgets.QPushButton(self)
+        btn_read_0xf1b2.setText('read')
+        btn_read_0xf1b2.setObjectName('btn_read_0xf1b2')
+        btn_write_0xf1b2 = QtWidgets.QPushButton(self)
+        btn_write_0xf1b2.setText('write')
+        btn_write_0xf1b2.setObjectName('btn_write_0xf1b2')
+
+        # 0xF1D0
+        lb_0xf1d0 = QtWidgets.QLabel(self)
+        lb_0xf1d0.setText('Base Model Part Number')
+        le_0xf1d0 = QtWidgets.QLineEdit(self)
+        le_0xf1d0.setObjectName('le_0xf1d0')
+        btn_read_0xf1d0 = QtWidgets.QPushButton(self)
+        btn_read_0xf1d0.setText('read')
+        btn_read_0xf1d0.setObjectName('btn_read_0xf1d0')
+
+        # 0xF1D1
+        lb_0xf1d1 = QtWidgets.QLabel(self)
+        lb_0xf1d1.setText('Base Model Part Number Version Code')
+        le_0xf1d1 = QtWidgets.QLineEdit(self)
+        le_0xf1d1.setObjectName('le_0xf1d1')
+        btn_read_0xf1d1 = QtWidgets.QPushButton(self)
+        btn_read_0xf1d1.setText('read')
+        btn_read_0xf1d1.setObjectName('btn_read_0xf1d1')
+
+        # 0xF1D2
+        lb_0xf1d2 = QtWidgets.QLabel(self)
+        lb_0xf1d2.setText('End Model Part Number')
+        le_0xf1d2 = QtWidgets.QLineEdit(self)
+        le_0xf1d2.setObjectName('le_0xf1d2')
+        btn_read_0xf1d2 = QtWidgets.QPushButton(self)
+        btn_read_0xf1d2.setText('read')
+        btn_read_0xf1d2.setObjectName('btn_read_0xf1d2')
+
+        # 0xF1D3
+        lb_0xf1d3 = QtWidgets.QLabel(self)
+        lb_0xf1d3.setText('End Model Part Number Version Code')
+        le_0xf1d3 = QtWidgets.QLineEdit(self)
+        le_0xf1d3.setObjectName('le_0xf1d3')
+        btn_read_0xf1d3 = QtWidgets.QPushButton(self)
+        btn_read_0xf1d3.setText('read')
+        btn_read_0xf1d3.setObjectName('btn_read_0xf1d3')
+
+        # 0xF0DF
+        lb_0xf0df = QtWidgets.QLabel(self)
+        lb_0xf0df.setText('Manufacturers Mode Flag')
+        cb_0xf0df = QtWidgets.QComboBox(self)
+        cb_0xf0df.setObjectName('cb_0xf0df')
+        cb_0xf0df.addItem('NonFactoryModel', 0)
+        cb_0xf0df.addItem('FactoryMode', 1)
+        btn_read_0xf0df = QtWidgets.QPushButton(self)
+        btn_read_0xf0df.setText('read')
+        btn_read_0xf0df.setObjectName('btn_read_0xf0df')
+        btn_write_0xf0df = QtWidgets.QPushButton(self)
+        btn_write_0xf0df.setText('write')
+        btn_write_0xf0df.setObjectName('btn_write_0xf0df')
+
+        # 0xF187
+        lb_0xf187 = QtWidgets.QLabel(self)
+        lb_0xf187.setText('ECU Part Number')
+        le_0xf187 = QtWidgets.QLineEdit(self)
+        le_0xf187.setObjectName('le_0xf187')
+        btn_read_0xf187 = QtWidgets.QPushButton(self)
+        btn_read_0xf187.setText('read')
+        btn_read_0xf187.setObjectName('btn_read_0xf187')
+
+        # 0xF195
+        lb_0xf195 = QtWidgets.QLabel(self)
+        lb_0xf195.setText('Supplier ECU Software Version Number')
+        le_0xf195 = QtWidgets.QLineEdit(self)
+        le_0xf195.setObjectName('le_0xf195')
+        btn_read_0xf195 = QtWidgets.QPushButton(self)
+        btn_read_0xf195.setText('read')
+        btn_read_0xf195.setObjectName('btn_read_0xf195')
+
+        # 0xF198
+        lb_0xf198 = QtWidgets.QLabel(self)
+        lb_0xf198.setText('Repair Shop Code')
+        le_0xf198 = QtWidgets.QLineEdit(self)
+        le_0xf198.setObjectName('le_0xf198')
+        btn_read_0xf198 = QtWidgets.QPushButton(self)
+        btn_read_0xf198.setText('read')
+        btn_read_0xf198.setObjectName('btn_read_0xf198')
+        btn_write_0xf198 = QtWidgets.QPushButton(self)
+        btn_write_0xf198.setText('write')
+        btn_write_0xf198.setObjectName('btn_write_0xf198')
+
+        # 0xF199
+        lb_0xf199 = QtWidgets.QLabel(self)
+        lb_0xf199.setText('Programming Date')
+        le_0xf199 = QtWidgets.QLineEdit(self)
+        le_0xf199.setObjectName('le_0xf199')
+        btn_read_0xf199 = QtWidgets.QPushButton(self)
+        btn_read_0xf199.setText('read')
+        btn_read_0xf199.setObjectName('btn_read_0xf199')
+        btn_write_0xf199 = QtWidgets.QPushButton(self)
+        btn_write_0xf199.setText('write')
+        btn_write_0xf199.setObjectName('btn_write_0xf199')
+
+        gl0 = QtWidgets.QGridLayout()
+
+        gl0.addWidget(lb_0xf190, 0, 0, 1, 1)
+        gl0.addWidget(le_0xf190, 0, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf190, 0, 2, 1, 1)
+        gl0.addWidget(btn_write_0xf190, 0, 3, 1, 1)
+
+        gl0.addWidget(lb_0xf1b1, 1, 0, 1, 1)
+        gl0.addWidget(le_0xf1b1, 1, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf1b1, 1, 2, 1, 1)
+
+        gl0.addWidget(lb_0xf1b2, 2, 0, 1, 1)
+        gl0.addWidget(le_0xf1b2, 2, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf1b2, 2, 2, 1, 1)
+        gl0.addWidget(btn_write_0xf1b2, 2, 3, 1, 1)
+
+        gl0.addWidget(lb_0xf1d0, 3, 0, 1, 1)
+        gl0.addWidget(le_0xf1d0, 3, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf1d0, 3, 2, 1, 1)
+
+        gl0.addWidget(lb_0xf1d1, 4, 0, 1, 1)
+        gl0.addWidget(le_0xf1d1, 4, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf1d1, 4, 2, 1, 1)
+
+        gl0.addWidget(lb_0xf1d2, 5, 0, 1, 1)
+        gl0.addWidget(le_0xf1d2, 5, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf1d2, 5, 2, 1, 1)
+
+        gl0.addWidget(lb_0xf1d3, 6, 0, 1, 1)
+        gl0.addWidget(le_0xf1d3, 6, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf1d3, 6, 2, 1, 1)
+
+        gl0.addWidget(lb_0xf0df, 7, 0, 1, 1)
+        gl0.addWidget(cb_0xf0df, 7, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf0df, 7, 2, 1, 1)
+        gl0.addWidget(btn_write_0xf0df, 7, 3, 1, 1)
+
+        gl0.addWidget(lb_0xf187, 8, 0, 1, 1)
+        gl0.addWidget(le_0xf187, 8, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf187, 8, 2, 1, 1)
+
+        gl0.addWidget(lb_0xf195, 9, 0, 1, 1)
+        gl0.addWidget(le_0xf195, 9, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf195, 9, 2, 1, 1)
+
+        gl0.addWidget(lb_0xf198, 10, 0, 1, 1)
+        gl0.addWidget(le_0xf198, 10, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf198, 10, 2, 1, 1)
+        gl0.addWidget(btn_write_0xf198, 10, 3, 1, 1)
+
+        gl0.addWidget(lb_0xf199, 11, 0, 1, 1)
+        gl0.addWidget(le_0xf199, 11, 1, 1, 1)
+        gl0.addWidget(btn_read_0xf199, 11, 2, 1, 1)
+        gl0.addWidget(btn_write_0xf199, 11, 3, 1, 1)
+
+        tb_log_display = QtWidgets.QTextBrowser(self)
+        tb_log_display.setObjectName('tb_log_display')
+
+        hb1 = QtWidgets.QHBoxLayout()
+        hb1.addWidget(tb_log_display)
+
+        vb = QtWidgets.QVBoxLayout()
+        vb.addLayout(hb0)
+        vb.addLayout(gl0)
+        vb.addLayout(hb1)
+
+        self.setLayout(vb)
+
+        QtCore.QMetaObject.connectSlotsByName(self)
+
+    @QtCore.pyqtSlot(str)
+    def on_cb_interface_type_currentIndexChanged(self, text):
+
+        if text == 'vector':
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(0, 'CH1/3')
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(1, 'CH2/4')
+        elif text == 'canalystii':
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(0, 'CAN1')
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(1, 'CAN2')
+
+    @QtCore.pyqtSlot()
+    def on_btn_open_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.open_device_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_close_clicked(self):
+        self.opened_flag = False
+        self.client = None
+        self.append_text_to_log_display('close device success')
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf190_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf190_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_write_0xf190_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.write_0xf190_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf1b1_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf1b1_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_write_0xf1b1_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.write_0xf1b1_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf1b2_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf1b2_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_write_0xf1b2_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.write_0xf1b2_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf1d0_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf1d0_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_write_0xf1d0_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.write_0xf1d0_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf1d1_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf1d1_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf1d2_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf1d2_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_write_0xf1d2_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.write_0xf1d2_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf1d3_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf1d3_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf0df_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf0df_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_write_0xf0df_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.write_0xf0df_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf187_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf187_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf195_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf195_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf198_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf198_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_write_0xf198_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.write_0xf198_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_read_0xf199_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.read_0xf199_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_write_0xf199_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.write_0xf199_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_write_0xf199_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.write_0xf199_task())
+
+    def append_text_to_log_display(self, text, color='#000000'):
+
+        text = '<font color="%s">%s</font>' % (color, text)
+        self.findChild(QtWidgets.QTextEdit, 'tb_log_display').append(text)
+
+    def clear_log_display(self):
+
+        self.findChild(QtWidgets.QTextEdit, 'tb_log_display').clear()
+
+    async def open_device_task(self):
+
+        if self.opened_flag is True:
+            self.append_text_to_log_display('already opened', '#ff0000')
+            return
+
+        interface_type_index = self.findChild(
+            QtWidgets.QComboBox, 'cb_interface_type').currentIndex()
+
+        baudrate_index = self.findChild(
+            QtWidgets.QComboBox, 'cb_baudrate').currentIndex()
+
+        can_channel_index = self.findChild(
+            QtWidgets.QComboBox, 'cb_can_channel').currentIndex()
+
+        # bus = VectorBus(channel=1, bitrate=500000, can_filters=can_filters)
+        # bus = CANalystIIBus(channel=0, baud=500000, can_filters=can_filters)
+
+        self.clear_log_display()
+
+        can_filters = [
+            {'can_id': self.rx_id, 'can_mask': 0xFFFFFFFF}
+        ]
+
+        bitrate = list(TIMING_DICT.keys())[baudrate_index]
+
+        try:
+            bus: BusABC = None
+            if interface_type_index == 0:
+                bus = VectorBus(channel=can_channel_index,
+                                bitrate=bitrate, can_filters=can_filters)
+
+            elif interface_type_index == 1:
+                bus = CANalystIIBus(channel=can_channel_index,
+                                    baud=bitrate, can_filters=can_filters)
+
+            network = aioisotp.ISOTPNetwork(bus=bus)
+
+            with network.open():
+                reader, writer = await network.open_connection(self.rx_id, self.tx_id)
+                client = Client(reader, writer)
+                client.config['data_identifiers'] = self.did_config
+
+                self.client = client
+
+                try:
+                    response = await client.tester_present(timeout=0.3)
+                except asyncio.TimeoutError:
+                    pass
+                except Exception as e:
+                    self.append_text_to_log_display('%s' % e, '#ff0000')
+
+                await client.change_session(3)
+                await client.request_seed(1)
+                await client.send_key(1, bytes([0x6d, 0xfb, 0x53, 0x20]))
+
+                self.append_text_to_log_display('open device success')
+                self.opened_flag = True
+
+                while self.opened_flag:
+                    await client.tester_present(True)
+                    await asyncio.sleep(1)
+                    # print('tester present')
+        except asyncio.TimeoutError:
+            self.append_text_to_log_display('open device failed', '#ff0000')
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf190_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf190)
+            data = response
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf190').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def write_0xf190_task(self):
+        try:
+            client = self.client
+            value = self.findChild(QtWidgets.QLineEdit, 'le_0xf190').text()
+            await client.write_data_by_identifier(0xf190, value)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf1b1_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf1b1)
+            data = response.strip()
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf1b1').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def write_0xf1b1_task(self):
+        try:
+            client = self.client
+            value = self.findChild(QtWidgets.QLineEdit, 'le_0xf1b1').text()
+            await client.write_data_by_identifier(0xf1b1, value)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf1b2_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf1b2)
+            data = response
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf1b2').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def write_0xf1b2_task(self):
+        try:
+            client = self.client
+            value = self.findChild(QtWidgets.QLineEdit, 'le_0xf1b2').text()
+            await client.write_data_by_identifier(0xf1b2, value)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf1d0_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf1d0)
+            data = response
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf1d0').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def write_0xf1d0_task(self):
+        try:
+            client = self.client
+            value = self.findChild(QtWidgets.QLineEdit, 'le_0xf1d0').text()
+            await client.write_data_by_identifier(0xf1d0, value)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf1d1_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf1d1)
+            data = response
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf1d1').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf1d2_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf1d2)
+            data = response
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf1d2').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def write_0xf1d2_task(self):
+        try:
+            client = self.client
+            value = self.findChild(QtWidgets.QLineEdit, 'le_0xf1d2').text()
+            await client.write_data_by_identifier(0xf1d2, value)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf1d3_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf1d3)
+            data = response
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf1d3').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf0df_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf0df)
+            data = response
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_0xf0df').setCurrentIndex(int(data))
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def write_0xf0df_task(self):
+        try:
+            client = self.client
+            value = self.findChild(QtWidgets.QComboBox,
+                                   'cb_0xf0df').currentIndex()
+            await client.write_data_by_identifier(0xf0df, value)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf187_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf187)
+            data = response
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf187').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf195_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf195)
+            data = response
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf195').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf198_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf198)
+            data = response
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf198').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def write_0xf198_task(self):
+        try:
+            client = self.client
+            value = self.findChild(QtWidgets.QLineEdit, 'le_0xf198').text()
+            await client.write_data_by_identifier(0xf198, value)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def read_0xf199_task(self):
+        try:
+            client = self.client
+            response = await client.read_data_by_identifier_first(0xf199)
+            data = response
+            self.findChild(QtWidgets.QLineEdit,
+                           'le_0xf199').setText(data)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def write_0xf199_task(self):
+        try:
+            client = self.client
+            value = self.findChild(QtWidgets.QLineEdit, 'le_0xf199').text()
+            await client.write_data_by_identifier(0xf199, value)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
diff --git a/widgets/diagnostic_trouble_code_widget.py b/widgets/diagnostic_trouble_code_widget.py
new file mode 100644
index 0000000..d93394e
--- /dev/null
+++ b/widgets/diagnostic_trouble_code_widget.py
@@ -0,0 +1,455 @@
+from PyQt5 import QtWidgets, QtCore, QtGui
+from can.interfaces.canalystii import CANalystIIBus, TIMING_DICT
+from can.interfaces.vector import VectorBus
+from can.bus import BusABC
+import asyncio
+import time
+import aioisotp
+from uds.client import Client
+from udsoncan import MemoryLocation
+import configparser
+
+
+dtc_describe = {
+    0x550122: 'P按键信号不正常',
+    0x550271: '马达动作故障',
+    0x550329: 'HOME侧传感器异常',
+    0x550429: 'M侧传感器故障',
+    0xD10017: '超过正常诊断电压',
+    0xD10116: '低于正常诊断电压',
+    0xC00188: 'BUS OFF',
+    0xC29187: '丢失TCU信号',
+    0xC14687: '丢失GW信号',
+}
+
+
+class DiagnosticTroubleCodeWidget(QtWidgets.QWidget):
+
+    def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags()):
+        super().__init__(parent=parent, flags=flags)
+        self.initUI()
+        self.client: Client = None
+        self.opened_flag = False
+        self.dtc_mask = 0
+
+        try:
+            config = configparser.ConfigParser()
+            config.read('config.ini')
+            self.tx_id = int(config['CAN_ID']['tx_id'], 16)
+            self.rx_id = int(config['CAN_ID']['rx_id'], 16)
+            self.append_text_to_log_display('read config.ini success')
+        except Exception as e:
+            self.append_text_to_log_display(
+                'read config.ini error, use 0x692 and 0x693 for default tx_id and rx_id', '#ff0000')
+            self.tx_id = 0x692
+            self.rx_id = 0x693
+
+    def initUI(self):
+
+        self.setWindowTitle('Diagnostic Trouble Code')
+        self.setFont(QtGui.QFont('Segoe UI'))
+
+        cb_interface_type = QtWidgets.QComboBox(self)
+        cb_interface_type.addItems(['vector', 'canalystii'])
+        cb_interface_type.setObjectName('cb_interface_type')
+
+        cb_baudrate = QtWidgets.QComboBox(self)
+        cb_baudrate.addItems([('%dK' % (i/1000)) for i in TIMING_DICT.keys()])
+        cb_baudrate.setCurrentIndex(14)
+        cb_baudrate.setObjectName('cb_baudrate')
+
+        cb_can_channel = QtWidgets.QComboBox(self)
+        cb_can_channel.addItems(['CH1/3', 'CH2/4'])
+        cb_can_channel.setObjectName('cb_can_channel')
+
+        btn_open = QtWidgets.QPushButton(self)
+        btn_open.setText('open')
+        btn_open.setObjectName('btn_open')
+        btn_close = QtWidgets.QPushButton(self)
+        btn_close.setText('close')
+        btn_close.setObjectName('btn_close')
+
+        hb0 = QtWidgets.QHBoxLayout()
+        hb0.addWidget(cb_interface_type)
+        hb0.addWidget(cb_baudrate)
+        hb0.addWidget(cb_can_channel)
+        hb0.addWidget(btn_open)
+        hb0.addWidget(btn_close)
+
+        btn_dtc_on = QtWidgets.QPushButton('DTC On', self)
+        btn_dtc_on.setObjectName('btn_dtc_on')
+        btn_dtc_off = QtWidgets.QPushButton('DTC Off', self)
+        btn_dtc_off.setObjectName('btn_dtc_off')
+        btn_clear_dtc = QtWidgets.QPushButton('Clear DTC', self)
+        btn_clear_dtc.setObjectName('btn_clear_dtc')
+
+        hb1 = QtWidgets.QHBoxLayout()
+        hb1.addWidget(btn_dtc_on)
+        hb1.addWidget(btn_dtc_off)
+        hb1.addWidget(btn_clear_dtc)
+
+        btn_get_number_of_dtc_by_status_mask = QtWidgets.QPushButton(self)
+        btn_get_number_of_dtc_by_status_mask.setText(
+            'get number of dtc by status_mask')
+        btn_get_number_of_dtc_by_status_mask.setObjectName(
+            'btn_get_number_of_dtc_by_status_mask')
+
+        btn_get_dtc_by_status_mask = QtWidgets.QPushButton(self)
+        btn_get_dtc_by_status_mask.setText('get dtc by status mask')
+        btn_get_dtc_by_status_mask.setObjectName('btn_get_dtc_by_status_mask')
+
+        btn_get_supported_dtc = QtWidgets.QPushButton(self)
+        btn_get_supported_dtc.setText('get supported dtc')
+        btn_get_supported_dtc.setObjectName('btn_get_supported_dtc')
+
+        hb2 = QtWidgets.QHBoxLayout()
+        hb2.addWidget(btn_get_number_of_dtc_by_status_mask)
+        hb2.addWidget(btn_get_dtc_by_status_mask)
+        hb2.addWidget(btn_get_supported_dtc)
+
+        cb_bit0 = QtWidgets.QCheckBox(self)
+        cb_bit0.setText('bit0 : testFailed')
+        cb_bit0.setObjectName('cb_bit0')
+        cb_bit1 = QtWidgets.QCheckBox(self)
+        cb_bit1.setText('bit1 : testFailedThisOperationCycle')
+        cb_bit1.setObjectName('cb_bit1')
+        cb_bit2 = QtWidgets.QCheckBox(self)
+        cb_bit2.setText('bit2 : pendingDTC')
+        cb_bit2.setObjectName('cb_bit2')
+        cb_bit3 = QtWidgets.QCheckBox(self)
+        cb_bit3.setText('bit3 : confirmedDTC')
+        cb_bit3.setObjectName('cb_bit3')
+
+        cb_bit4 = QtWidgets.QCheckBox(self)
+        cb_bit4.setText('bit4 : testNotCompletedSinceLastClear')
+        cb_bit4.setObjectName('cb_bit4')
+        cb_bit5 = QtWidgets.QCheckBox(self)
+        cb_bit5.setText('bit5 : testFailedSinceLastClear')
+        cb_bit5.setObjectName('cb_bit5')
+        cb_bit6 = QtWidgets.QCheckBox(self)
+        cb_bit6.setText('bit6 : testNotCompletedThisOperationCycle')
+        cb_bit6.setObjectName('cb_bit6')
+        cb_bit7 = QtWidgets.QCheckBox(self)
+        cb_bit7.setText('bit7 : warningIndicatorRequested')
+        cb_bit7.setObjectName('cb_bit7')
+
+        gl0 = QtWidgets.QGridLayout()
+
+        gl0.addWidget(cb_bit0, 0, 0, 1, 1)
+        gl0.addWidget(cb_bit1, 0, 1, 1, 1)
+        gl0.addWidget(cb_bit2, 0, 2, 1, 1)
+        gl0.addWidget(cb_bit3, 0, 3, 1, 1)
+        gl0.addWidget(cb_bit4, 1, 0, 1, 1)
+        gl0.addWidget(cb_bit5, 1, 1, 1, 1)
+        gl0.addWidget(cb_bit6, 1, 2, 1, 1)
+        gl0.addWidget(cb_bit7, 1, 3, 1, 1)
+
+        gb_dtc_bits = QtWidgets.QGroupBox('dtc mask bits', self)
+        gb_dtc_bits.setLayout(gl0)
+
+        hb3 = QtWidgets.QHBoxLayout()
+        hb3.addWidget(gb_dtc_bits)
+
+        tb_log_display = QtWidgets.QTextBrowser(self)
+        tb_log_display.setObjectName('tb_log_display')
+
+        hb4 = QtWidgets.QHBoxLayout()
+        hb4.addWidget(tb_log_display)
+
+        vb = QtWidgets.QVBoxLayout()
+        vb.addLayout(hb0)
+        vb.addLayout(hb1)
+        vb.addLayout(hb2)
+        vb.addLayout(hb3)
+        vb.addLayout(hb4)
+
+        self.setLayout(vb)
+
+        QtCore.QMetaObject.connectSlotsByName(self)
+
+    @QtCore.pyqtSlot(str)
+    def on_cb_interface_type_currentIndexChanged(self, text):
+
+        if text == 'vector':
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(0, 'CH1/3')
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(1, 'CH2/4')
+        elif text == 'canalystii':
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(0, 'CAN1')
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(1, 'CAN2')
+
+    @QtCore.pyqtSlot()
+    def on_btn_open_clicked(self):
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.open_device_task())
+
+    @QtCore.pyqtSlot()
+    def on_btn_close_clicked(self):
+        self.opened_flag = False
+        self.client = None
+        self.append_text_to_log_display('close device success')
+
+    @QtCore.pyqtSlot()
+    def on_btn_dtc_on_clicked(self):
+        if self.opened_flag:
+            loop = asyncio.get_event_loop()
+            loop.create_task(self.control_dtc_task(1))
+        else:
+            self.append_text_to_log_display(
+                'please open the device', '#ff0000')
+
+    @QtCore.pyqtSlot()
+    def on_btn_dtc_off_clicked(self):
+        if self.opened_flag:
+            loop = asyncio.get_event_loop()
+            loop.create_task(self.control_dtc_task(2))
+        else:
+            self.append_text_to_log_display(
+                'please open the device', '#ff0000')
+
+    @QtCore.pyqtSlot()
+    def on_btn_clear_dtc_clicked(self):
+        if self.opened_flag:
+            loop = asyncio.get_event_loop()
+            loop.create_task(self.clear_dtc_task())
+        else:
+            self.append_text_to_log_display(
+                'please open the device', '#ff0000')
+
+    @QtCore.pyqtSlot()
+    def on_btn_get_number_of_dtc_by_status_mask_clicked(self):
+        if self.opened_flag:
+            loop = asyncio.get_event_loop()
+            loop.create_task(self.get_number_of_dtc_by_status_mask_task())
+        else:
+            self.append_text_to_log_display(
+                'please open the device', '#ff0000')
+
+    @QtCore.pyqtSlot()
+    def on_btn_get_dtc_by_status_mask_clicked(self):
+        if self.opened_flag:
+            loop = asyncio.get_event_loop()
+            loop.create_task(self.get_dtc_by_status_mask_task())
+        else:
+            self.append_text_to_log_display(
+                'please open the device', '#ff0000')
+
+    @QtCore.pyqtSlot()
+    def on_btn_get_supported_dtc_clicked(self):
+        if self.opened_flag:
+            loop = asyncio.get_event_loop()
+            loop.create_task(self.get_supported_dtc_task())
+        else:
+            self.append_text_to_log_display(
+                'please open the device', '#ff0000')
+
+    @QtCore.pyqtSlot(int)
+    def on_cb_bit0_stateChanged(self, state):
+        if state == 0:
+            self.dtc_mask &= ~(1 << 0)
+        else:
+            self.dtc_mask |= 1 << 0
+
+    @QtCore.pyqtSlot(int)
+    def on_cb_bit1_stateChanged(self, state):
+        if state == 0:
+            self.dtc_mask &= ~(1 << 1)
+        else:
+            self.dtc_mask |= 1 << 1
+
+    @QtCore.pyqtSlot(int)
+    def on_cb_bit2_stateChanged(self, state):
+        if state == 0:
+            self.dtc_mask &= ~(1 << 2)
+        else:
+            self.dtc_mask |= 1 << 2
+
+    @QtCore.pyqtSlot(int)
+    def on_cb_bit3_stateChanged(self, state):
+        if state == 0:
+            self.dtc_mask &= ~(1 << 3)
+        else:
+            self.dtc_mask |= 1 << 3
+
+    @QtCore.pyqtSlot(int)
+    def on_cb_bit4_stateChanged(self, state):
+        if state == 0:
+            self.dtc_mask &= ~(1 << 4)
+        else:
+            self.dtc_mask |= 1 << 4
+
+    @QtCore.pyqtSlot(int)
+    def on_cb_bit5_stateChanged(self, state):
+        if state == 0:
+            self.dtc_mask &= ~(1 << 5)
+        else:
+            self.dtc_mask |= 1 << 5
+
+    @QtCore.pyqtSlot(int)
+    def on_cb_bit6_stateChanged(self, state):
+        if state == 0:
+            self.dtc_mask &= ~(1 << 6)
+        else:
+            self.dtc_mask |= 1 << 6
+
+    @QtCore.pyqtSlot(int)
+    def on_cb_bit7_stateChanged(self, state):
+        if state == 0:
+            self.dtc_mask &= ~(1 << 7)
+        else:
+            self.dtc_mask |= 1 << 7
+
+    def append_text_to_log_display(self, text, color='#000000'):
+
+        text = '<font color="%s">%s</font>' % (color, text)
+        self.findChild(QtWidgets.QTextEdit, 'tb_log_display').append(text)
+
+    def clear_log_display(self):
+
+        self.findChild(QtWidgets.QTextEdit, 'tb_log_display').clear()
+
+    async def open_device_task(self):
+
+        if self.opened_flag is True:
+            self.append_text_to_log_display('already opened', '#ff0000')
+            return
+
+        interface_type_index = self.findChild(
+            QtWidgets.QComboBox, 'cb_interface_type').currentIndex()
+
+        baudrate_index = self.findChild(
+            QtWidgets.QComboBox, 'cb_baudrate').currentIndex()
+
+        can_channel_index = self.findChild(
+            QtWidgets.QComboBox, 'cb_can_channel').currentIndex()
+
+        # bus = VectorBus(channel=1, bitrate=500000, can_filters=can_filters)
+        # bus = CANalystIIBus(channel=0, baud=500000, can_filters=can_filters)
+
+        self.clear_log_display()
+
+        can_filters = [
+            {'can_id': self.rx_id, 'can_mask': 0xFFFFFFFF}
+        ]
+
+        bitrate = list(TIMING_DICT.keys())[baudrate_index]
+
+        try:
+            bus: BusABC = None
+            if interface_type_index == 0:
+                bus = VectorBus(channel=can_channel_index,
+                                bitrate=bitrate, can_filters=can_filters)
+
+            elif interface_type_index == 1:
+                bus = CANalystIIBus(channel=can_channel_index,
+                                    baud=bitrate, can_filters=can_filters)
+
+            network = aioisotp.ISOTPNetwork(bus=bus)
+
+            with network.open():
+                reader, writer = await network.open_connection(self.rx_id, self.tx_id)
+                client = Client(reader, writer)
+
+                self.client = client
+
+                try:
+                    response = await client.tester_present(timeout=0.3)
+                except asyncio.TimeoutError:
+                    pass
+                except Exception as e:
+                    print(e)
+
+                await client.change_session(3)
+                await client.request_seed(1)
+                await client.send_key(1, bytes([0x6d, 0xfb, 0x53, 0x20]))
+
+                self.append_text_to_log_display('open device success')
+                self.opened_flag = True
+
+                while self.opened_flag:
+                    await client.tester_present(True)
+                    await asyncio.sleep(1)
+                    # print('tester present')
+        except asyncio.TimeoutError:
+            self.append_text_to_log_display('open device failed', '#ff0000')
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def control_dtc_task(self, setting_type):
+        try:
+            response = await self.client.control_dtc_setting(setting_type)
+            # print(response)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def clear_dtc_task(self, group=0xffffff):
+        try:
+            response = await self.client.clear_dtc(group)
+            # print(response)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def get_number_of_dtc_by_status_mask_task(self):
+
+        if self.dtc_mask == 0:
+            self.append_text_to_log_display('dtc mask is empty', '#ff0000')
+            return
+
+        try:
+            self.client.writer.write(bytes([0x19, 0x01, self.dtc_mask]))
+            payload = await asyncio.wait_for(self.client.reader.read(4095), 1)
+            self.append_text_to_log_display(
+                '>>> 0x1901 get_number_of_dtc_by_status_mask')
+            self.append_text_to_log_display('Status Mask:0x%.2x DTC Format:0x%.2x DTC Count:%d' % (
+                payload[2], payload[3], payload[4] << 8 | payload[5]))
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def get_dtc_by_status_mask_task(self):
+
+        if self.dtc_mask == 0:
+            self.append_text_to_log_display('dtc mask is empty', '#ff0000')
+            return
+
+        try:
+            self.client.writer.write(bytes([0x19, 0x02, self.dtc_mask]))
+            payload = await asyncio.wait_for(self.client.reader.read(4095), 1)
+            self.append_text_to_log_display(
+                '>>> 0x1902 get_dtc_by_status_mask')
+            for i in range(len(payload[3:]) // 4):
+                a = i * 4 + 3
+                b = i * 4 + 4
+                c = i * 4 + 5
+                d = i * 4 + 6
+                dtc_hex = payload[a] << 16 | payload[b] << 8 | payload[c] << 0
+                log = 'DTC:0x%.2x%.2x%.2x Status:0x%.2x %s ' % (
+                    payload[a], payload[b], payload[c], payload[d], dtc_describe[dtc_hex])
+                for j in range(8):
+                    if payload[d] >> j & 0x1:
+                        log += 'bit%d ' % j
+                self.append_text_to_log_display(log)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    async def get_supported_dtc_task(self):
+        try:
+            self.client.writer.write(bytes([0x19, 0x0a]))
+            payload = await asyncio.wait_for(self.client.reader.read(4095), 1)
+            self.append_text_to_log_display('>>> 0x190a get_supported_dtc')
+            for i in range(len(payload[3:]) // 4):
+                a = i * 4 + 3
+                b = i * 4 + 4
+                c = i * 4 + 5
+                d = i * 4 + 6
+                dtc_hex = payload[a] << 16 | payload[b] << 8 | payload[c] << 0
+                log = 'DTC:0x%.2x%.2x%.2x Status:0x%.2x %s ' % (
+                    payload[a], payload[b], payload[c], payload[d], dtc_describe[dtc_hex])
+                for j in range(8):
+                    if payload[d] >> j & 0x1:
+                        log += 'bit%d ' % j
+                self.append_text_to_log_display(log)
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
diff --git a/widgets/flash_bootloader_widget.py b/widgets/flash_bootloader_widget.py
new file mode 100644
index 0000000..855d4d3
--- /dev/null
+++ b/widgets/flash_bootloader_widget.py
@@ -0,0 +1,504 @@
+from PyQt5 import QtWidgets, QtCore, QtGui
+from can.interfaces.canalystii import CANalystIIBus, TIMING_DICT
+from can.interfaces.vector import VectorBus
+from can.bus import BusABC
+import asyncio
+import time
+import aioisotp
+from uds.client import Client
+from udsoncan import MemoryLocation
+import configparser
+
+
+class FlashBootloaderWidget(QtWidgets.QWidget):
+
+    def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags()):
+        super().__init__(parent=parent, flags=flags)
+        self.initUI()
+        self.in_programming = False
+        self.bus: BusABC = None
+        self.drv_data: bytes = None
+        self.app_data: bytes = None
+
+        try:
+            config = configparser.ConfigParser()
+            config.read('config.ini')
+            self.tx_id = int(config['CAN_ID']['tx_id'], 16)
+            self.rx_id = int(config['CAN_ID']['rx_id'], 16)
+            self.append_text_to_log_display('read config.ini success')
+        except Exception as e:
+            self.append_text_to_log_display(
+                'read config.ini error, use 0x692 and 0x693 for default tx_id and rx_id', '#ff0000')
+            self.tx_id = 0x692
+            self.rx_id = 0x693
+
+    def initUI(self):
+
+        self.setWindowTitle('Flash Bootloader')
+        self.setFont(QtGui.QFont('Segoe UI'))
+
+        le_drv = QtWidgets.QLineEdit(self)
+        le_drv.setReadOnly(True)
+        le_drv.setObjectName('le_drv')
+        btn_drv = QtWidgets.QPushButton(self)
+        btn_drv.setText('driver')
+        btn_drv.setObjectName('btn_drv')
+
+        le_app = QtWidgets.QLineEdit(self)
+        le_app.setReadOnly(True)
+        le_app.setObjectName('le_app')
+        btn_app = QtWidgets.QPushButton(self)
+        btn_app.setText('app')
+        btn_app.setObjectName('btn_app')
+
+        gl0 = QtWidgets.QGridLayout()
+        gl0.addWidget(le_drv, 0, 0, 1, 1)
+        gl0.addWidget(btn_drv, 0, 1, 1, 1)
+        gl0.addWidget(le_app, 1, 0, 1, 1)
+        gl0.addWidget(btn_app, 1, 1, 1, 1)
+
+        cb_interface_type = QtWidgets.QComboBox(self)
+        cb_interface_type.addItems(['vector', 'canalystii'])
+        cb_interface_type.setObjectName('cb_interface_type')
+
+        cb_baudrate = QtWidgets.QComboBox(self)
+        cb_baudrate.addItems([('%dK' % (i/1000)) for i in TIMING_DICT.keys()])
+        cb_baudrate.setCurrentIndex(14)
+        cb_baudrate.setObjectName('cb_baudrate')
+
+        cb_can_channel = QtWidgets.QComboBox(self)
+        cb_can_channel.addItems(['CH1/3', 'CH2/4'])
+        cb_can_channel.setObjectName('cb_can_channel')
+
+        btn_open = QtWidgets.QPushButton(self)
+        btn_open.setText('open')
+        btn_open.setObjectName('btn_open')
+
+        hb0 = QtWidgets.QHBoxLayout()
+        hb0.addWidget(cb_interface_type)
+        hb0.addWidget(cb_baudrate)
+        hb0.addWidget(cb_can_channel)
+        hb0.addWidget(btn_open)
+
+        cb_only_main = QtWidgets.QCheckBox(self)
+        cb_only_main.setText('only programming step')
+
+        btn_start = QtWidgets.QPushButton(self)
+        btn_start.setText('start programming')
+        btn_start.setObjectName('btn_start')
+        btn_start.setDisabled(True)
+
+        hb1 = QtWidgets.QHBoxLayout()
+        hb1.addWidget(cb_only_main)
+        hb1.addWidget(btn_start)
+
+        progress_bar = QtWidgets.QProgressBar()
+        progress_bar.setObjectName('progress_bar')
+
+        hb2 = QtWidgets.QHBoxLayout()
+        hb2.addWidget(progress_bar)
+
+        checkbox1 = QtWidgets.QCheckBox(self)
+        checkbox2 = QtWidgets.QCheckBox(self)
+        checkbox3 = QtWidgets.QCheckBox(self)
+        checkbox4 = QtWidgets.QCheckBox(self)
+        checkbox5 = QtWidgets.QCheckBox(self)
+        checkbox6 = QtWidgets.QCheckBox(self)
+        checkbox7 = QtWidgets.QCheckBox(self)
+        checkbox8 = QtWidgets.QCheckBox(self)
+        checkbox9 = QtWidgets.QCheckBox(self)
+        checkbox10 = QtWidgets.QCheckBox(self)
+        checkbox11 = QtWidgets.QCheckBox(self)
+
+        # https://blog.csdn.net/jianfengxia/article/details/86623321
+
+        checkbox1.setText('Enter extended session')
+        checkbox2.setText('Stop setting of DTCs')
+        checkbox3.setText('Disable non-diagnostic communication')
+        vb_pre = QtWidgets.QVBoxLayout()
+        vb_pre.setAlignment(QtCore.Qt.AlignTop)
+        vb_pre.addWidget(checkbox1)
+        vb_pre.addWidget(checkbox2)
+        vb_pre.addWidget(checkbox3)
+        gb_pre = QtWidgets.QGroupBox(self)
+        gb_pre.setTitle('pre-programming')
+        gb_pre.setLayout(vb_pre)
+
+        checkbox4.setText('Enter programming session')
+        checkbox5.setText('Request seed')
+        checkbox6.setText('Send key')
+        checkbox7.setText('Write Programming Date')
+        checkbox8.setText('Erase Application Software Memory')
+        checkbox9.setText('Download Application Software')
+        checkbox10.setText(
+            'Check Programming Application Software Dependencie')
+
+        vb_main = QtWidgets.QVBoxLayout()
+        vb_main.setAlignment(QtCore.Qt.AlignTop)
+        vb_main.addWidget(checkbox4)
+        vb_main.addWidget(checkbox5)
+        vb_main.addWidget(checkbox6)
+        vb_main.addWidget(checkbox7)
+        vb_main.addWidget(checkbox8)
+        vb_main.addWidget(checkbox9)
+        vb_main.addWidget(checkbox10)
+        gb_main = QtWidgets.QGroupBox(self)
+        gb_main.setTitle('programming')
+        gb_main.setLayout(vb_main)
+
+        checkbox11.setText('ECUReset')
+        vb_post = QtWidgets.QVBoxLayout()
+        vb_post.setAlignment(QtCore.Qt.AlignTop)
+        vb_post.addWidget(checkbox11)
+        gb_post = QtWidgets.QGroupBox(self)
+        gb_post.setTitle('post-programming')
+        gb_post.setLayout(vb_post)
+
+        hb3 = QtWidgets.QHBoxLayout()
+        hb3.addWidget(gb_pre)
+        hb3.addWidget(gb_main)
+        hb3.addWidget(gb_post)
+
+        tb_log_display = QtWidgets.QTextBrowser(self)
+        tb_log_display.setObjectName('tb_log_display')
+
+        hb4 = QtWidgets.QHBoxLayout()
+        hb4.addWidget(tb_log_display)
+
+        vb = QtWidgets.QVBoxLayout()
+        vb.addLayout(gl0)
+        vb.addLayout(hb0)
+        vb.addLayout(hb1)
+        vb.addLayout(hb2)
+        vb.addLayout(hb3)
+        vb.addLayout(hb4)
+
+        self.setLayout(vb)
+
+        QtCore.QMetaObject.connectSlotsByName(self)
+
+    async def pre_programming(self, client: Client):
+
+        self.append_text_to_log_display('# 预编程步骤')
+
+        # 进入extended session
+        self.append_text_to_log_display('>>> 进入extended session')
+        response = await client.change_session(3)
+        # print(response)
+
+        # 检查编程条件
+        self.append_text_to_log_display('>>> 检查编程条件')
+        response = await client.start_routine(0xff02)
+        # print(response)
+
+        # 关闭DTC的存储
+        self.append_text_to_log_display('>>> 关闭DTC的存储')
+        response = await client.control_dtc_setting(2)
+        # print(response)
+
+        # 关闭与诊断无关的报文
+        self.append_text_to_log_display('>>> 关闭与诊断无关的报文')
+        response = await client.communication_control(0x03, 0x01)
+        # print(response)
+
+    async def programming(self, client: Client):
+
+        self.append_text_to_log_display('# 主编程步骤')
+
+        # 进入programming session
+        self.append_text_to_log_display('>>> 进入programming session')
+        response = await client.change_session(2)
+        # print(response)
+
+        # 请求种子
+        self.append_text_to_log_display('>>> 请求种子')
+        response = await client.request_seed(1)
+        # print(response)
+
+        # 发送密钥
+        self.append_text_to_log_display('>>> 发送密钥')
+        response = await client.send_key(1, bytes([0xb3, 0x42]))
+        # print(response)
+
+        # 发送driver文件
+        self.append_text_to_log_display('>>> 发送driver文件')
+        drv_file = self.drv_data
+        address = drv_file[16] << 24 | drv_file[17] << 16 | drv_file[18] << 8 | drv_file[19] << 0
+        memorysize = drv_file[20] << 24 | drv_file[21] << 16 | drv_file[22] << 8 | drv_file[23] << 0
+        memory_location = MemoryLocation(address, memorysize, 32, 32)
+        response = await client.request_download(memory_location)
+        # print(response)
+
+        max_length = response.service_data.max_length
+
+        # 有效数据长度, 去除sid和sequence两个字节
+        payload_length = max_length - 2
+
+        count = (len(drv_file) + payload_length - 1) // payload_length
+
+        base = self.get_progressbar_pos()
+        for i in range(count):
+            start = i * payload_length
+            end = start + payload_length
+            response = await client.transfer_data((i + 1) % 256, drv_file[start:end])
+            self.set_progressbar_pos(base + end)
+            # print(response)
+
+        response = await client.request_transfer_exit()
+        # print(response)
+
+        # driver文件完整性检验
+        self.append_text_to_log_display('>>> driver文件完整性检验')
+        response = await client.start_routine(0xf001, drv_file[0:4])
+        # print(response)
+
+        app_file = self.app_data
+        address = app_file[16] << 24 | app_file[17] << 16 | app_file[18] << 8 | app_file[19] << 0
+        memorysize = app_file[20] << 24 | app_file[21] << 16 | app_file[22] << 8 | app_file[23] << 0
+        memory_location = MemoryLocation(address, memorysize, 32, 32)
+
+        # 删除app存储空间
+        self.append_text_to_log_display('>>> 删除app存储空间')
+        data = b''
+        data += memory_location.alfid.get_byte()
+        data += memory_location.get_address_bytes()
+        data += memory_location.get_memorysize_bytes()
+        response = await client.start_routine(0xff00, data)
+        # print(response)
+
+        # 发送app文件
+        self.append_text_to_log_display('>>> 发送app文件')
+        response = await client.request_download(memory_location)
+        # print(response)
+
+        max_length = response.service_data.max_length
+
+        # 有效数据长度, 去除sid和sequence两个字节
+        payload_length = max_length - 2
+
+        count = (len(app_file) + payload_length - 1) // payload_length
+
+        base = self.get_progressbar_pos()
+        for i in range(count):
+            start = i * payload_length
+            end = start + payload_length
+            response = await client.transfer_data((i + 1) % 256, app_file[start:end])
+            self.set_progressbar_pos(base + end)
+            # print(response)
+
+        response = await client.request_transfer_exit()
+        # print(response)
+
+        # app文件完整性检验
+        self.append_text_to_log_display('>>> app文件完整性检验')
+        response = await client.start_routine(0xf001, app_file[0:4])
+        # print(response)
+
+    async def post_programming(self, client: Client):
+
+        self.append_text_to_log_display('# 后编程步骤')
+
+        # 回到default session
+        self.append_text_to_log_display('>>> 回到default session')
+        response = await client.change_session(1, True)
+        # print(response)
+
+    async def start_programming(self):
+
+        network = aioisotp.ISOTPNetwork(bus=self.bus)
+
+        with network.open():
+
+            reader, writer = await network.open_connection(self.rx_id, self.tx_id)
+
+            client = Client(reader, writer)
+
+            try:
+                await client.tester_present(timeout=0.3)
+            except asyncio.TimeoutError:
+                # canalystii 第一条请求无效
+                # timeout >= 0.3
+                if isinstance(self.bus, CANalystIIBus):
+                    pass
+                elif isinstance(self.bus, VectorBus):
+                    self.append_text_to_log_display('查看是否选错CAN通道', '#ff0000')
+
+            except Exception as e:
+                self.append_text_to_log_display('%s' % e, '#ff0000')
+
+            self.set_progressbar_pos(0)
+
+            t1 = time.time()
+
+            try:
+
+                await self.pre_programming(client)
+                await self.programming(client)
+                await self.post_programming(client)
+
+            except asyncio.TimeoutError:
+
+                self.append_text_to_log_display('request timeout', '#ff0000')
+
+            except Exception as e:
+
+                self.append_text_to_log_display('%s' % e, '#ff0000')
+
+            t2 = time.time()
+
+            self.append_text_to_log_display('finished in %.2f sec' % (t2 - t1))
+            self.in_programming = False
+            self.findChild(QtWidgets.QPushButton,
+                           'btn_start').setDisabled(True)
+
+    def set_progressbar_pos(self, pos):
+
+        drv_data_len = len(
+            self.drv_data) if self.drv_data is not None else 0
+        app_data_len = len(
+            self.app_data) if self.app_data is not None else 0
+        total_len = drv_data_len + app_data_len
+        if pos > total_len:
+            pos = total_len
+        elif pos < 0:
+            pos = 0
+
+        self.findChild(QtWidgets.QProgressBar, 'progress_bar').setValue(pos)
+
+    def get_progressbar_pos(self):
+
+        return self.findChild(QtWidgets.QProgressBar, 'progress_bar').value()
+
+    def append_text_to_log_display(self, text, color='#000000'):
+
+        text = '<font color="%s">%s</font>' % (color, text)
+        self.findChild(QtWidgets.QTextBrowser, 'tb_log_display').append(text)
+
+    def clear_log_display(self):
+
+        self.findChild(QtWidgets.QTextBrowser, 'tb_log_display').clear()
+
+    @QtCore.pyqtSlot()
+    def on_btn_drv_clicked(self):
+
+        if self.in_programming:
+            self.append_text_to_log_display('## 正在编程中')
+            return
+
+        fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
+            self, 'load driver file', '.', 'All Files (*);;Bin Files (*.bin)')
+
+        if fileName != '':
+            self.findChild(QtWidgets.QLineEdit, 'le_drv').setText(fileName)
+            with open(fileName, 'rb') as fd:
+                self.drv_data = fd.read()
+                drv_data_len = len(
+                    self.drv_data) if self.drv_data is not None else 0
+                app_data_len = len(
+                    self.app_data) if self.app_data is not None else 0
+                total_len = drv_data_len + app_data_len
+                self.findChild(QtWidgets.QProgressBar,
+                               'progress_bar').setRange(0, total_len)
+
+    @QtCore.pyqtSlot()
+    def on_btn_app_clicked(self):
+
+        if self.in_programming:
+            self.append_text_to_log_display('## 正在编程中')
+            return
+
+        fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
+            self, 'load app file', '.', 'All Files (*);;Bin Files (*.bin)')
+
+        if fileName != '':
+            self.findChild(QtWidgets.QLineEdit, 'le_app').setText(fileName)
+            with open(fileName, 'rb') as fd:
+                self.app_data = fd.read()
+                drv_data_len = len(
+                    self.drv_data) if self.drv_data is not None else 0
+                app_data_len = len(
+                    self.app_data) if self.app_data is not None else 0
+                total_len = drv_data_len + app_data_len
+                self.findChild(QtWidgets.QProgressBar,
+                               'progress_bar').setRange(0, total_len)
+
+    @QtCore.pyqtSlot()
+    def on_btn_start_clicked(self):
+
+        if self.drv_data is None or self.app_data is None:
+            self.append_text_to_log_display('请加载driver文件和app文件')
+            return
+
+        if self.bus is None:
+            self.append_text_to_log_display('请打开设备')
+            return
+
+        if self.in_programming:
+            self.append_text_to_log_display('## 正在编程中')
+            return
+
+        self.in_programming = True
+        loop = asyncio.get_event_loop()
+        loop.create_task(self.start_programming())
+
+    @QtCore.pyqtSlot()
+    def on_btn_open_clicked(self):
+
+        if self.in_programming:
+            self.append_text_to_log_display('## 正在编程中')
+            return
+
+        interface_type_index = self.findChild(
+            QtWidgets.QComboBox, 'cb_interface_type').currentIndex()
+
+        baudrate_index = self.findChild(
+            QtWidgets.QComboBox, 'cb_baudrate').currentIndex()
+
+        can_channel_index = self.findChild(
+            QtWidgets.QComboBox, 'cb_can_channel').currentIndex()
+
+        # bus = VectorBus(channel=1, bitrate=500000, can_filters=can_filters)
+        # bus = CANalystIIBus(channel=0, baud=500000, can_filters=can_filters)
+
+        self.clear_log_display()
+
+        can_filters = [
+            {'can_id': self.rx_id, 'can_mask': 0xFFFFFFFF}
+        ]
+
+        bitrate = list(TIMING_DICT.keys())[baudrate_index]
+
+        try:
+
+            if interface_type_index == 0:
+                self.bus = VectorBus(channel=can_channel_index,
+                                     bitrate=bitrate, can_filters=can_filters)
+
+            elif interface_type_index == 1:
+                self.bus = CANalystIIBus(channel=can_channel_index,
+                                         baud=bitrate, can_filters=can_filters)
+
+            self.append_text_to_log_display('open device success')
+            self.findChild(QtWidgets.QPushButton,
+                           'btn_start').setDisabled(False)
+
+        except Exception as e:
+            self.append_text_to_log_display('%s' % e, '#ff0000')
+
+    @QtCore.pyqtSlot(str)
+    def on_cb_interface_type_currentIndexChanged(self, text):
+
+        if text == 'vector':
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(0, 'CH1/3')
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(1, 'CH2/4')
+        elif text == 'canalystii':
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(0, 'CAN1')
+            self.findChild(QtWidgets.QComboBox,
+                           'cb_can_channel').setItemText(1, 'CAN2')
+                    
+# @PyQt5.QtCore.pyqtSlot(参数)
+# def on_发送者对象名称_发射信号名称(self, 参数):
+#     pass
diff --git a/widgets/main_widget.py b/widgets/main_widget.py
new file mode 100644
index 0000000..68eead9
--- /dev/null
+++ b/widgets/main_widget.py
@@ -0,0 +1,56 @@
+from PyQt5 import QtWidgets, QtGui, QtCore
+
+from widgets.flash_bootloader_widget import FlashBootloaderWidget
+from widgets.data_identifier_widget import DataIdentifierWidget
+from widgets.diagnostic_trouble_code_widget import DiagnosticTroubleCodeWidget
+
+
+class MainWidget(QtWidgets.QWidget):
+
+    def __init__(self):
+        super().__init__()
+        self.initUI()
+        # self.move_center()
+
+    def initUI(self):
+
+        self.setFont(QtGui.QFont('Segoe UI'))
+        self.setWindowTitle('Flash Bootloader')
+
+        flash_bootloader_widget = FlashBootloaderWidget(self)
+        data_identifier_widget = DataIdentifierWidget(self)
+        diagnostic_trouble_code_widget = DiagnosticTroubleCodeWidget(self)
+
+        tab_widget = QtWidgets.QTabWidget(self)
+        tab_widget.setObjectName('tab_widget')
+        tab_widget.addTab(flash_bootloader_widget, 'Flash Bootloader')
+        tab_widget.addTab(data_identifier_widget, 'Data Identifier')
+        tab_widget.addTab(diagnostic_trouble_code_widget,
+                          'Diagnostic Trouble Code')
+
+        hb0 = QtWidgets.QHBoxLayout()
+        hb0.addWidget(tab_widget)
+
+        vb = QtWidgets.QVBoxLayout()
+        vb.addLayout(hb0)
+
+        self.setLayout(vb)
+
+        QtCore.QMetaObject.connectSlotsByName(self)
+
+    def move_center(self):
+
+        w = QtWidgets.QApplication.desktop().width()
+        h = QtWidgets.QApplication.desktop().height()
+        ww = self.width()
+        hh = self.height()
+        self.move((w - ww) / 2, (h - hh) / 2)
+
+    @QtCore.pyqtSlot(int)
+    def on_tab_widget_currentChanged(self, index):
+        if index == 0:
+            self.setWindowTitle('Flash Bootloader')
+        elif index == 1:
+            self.setWindowTitle('Data Identifier')
+        elif index == 2:
+            self.setWindowTitle('Diagnostic Trouble Code')

--
Gitblit v1.8.0