├── .gitignore ├── LICENSE ├── README.md ├── from_vendor ├── README.md ├── TestDataModel.java ├── anjian_config.json ├── funOl_UT161B.json ├── funOl_UT161D.json ├── funOl_UT161E.json ├── funOl_UT60BT.json ├── funOl_UT61B+.json ├── funOl_UT61D+.json └── funOl_UT61E+.json ├── getUnits.py ├── mqtt_bridge ├── plotting-live-data.py ├── readDMM.py ├── requirements.txt └── ut61eplus ├── __init__.py └── ut61eplus.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unit_ut61eplus 2 | Access digtal multimeter UNI-T UT61E+ from Python via CP2110 based USB connection 3 | 4 | It should work also for other models: 5 | 6 | * UT60BT 7 | * UT61B+ 8 | * UT61D+ 9 | * UT161B 10 | * UT161D 11 | * UT161E 12 | 13 | but for this the unit conversion has to be adjusted (see `from_vendor/*.json`) 14 | 15 | ## Status 16 | > Working with Linux, patches/documentation for Windows are welcome (probably only docs needed to setup the HID library correctly) 17 | 18 | 19 | ## Example output (see `readDMM.py`) 20 | ``` 21 | mode=DCV 22 | range=1 23 | display=8.595 24 | overload=False 25 | decimal=8.595 26 | unit=V 27 | max=False 28 | min=False 29 | hold=False 30 | rel=False 31 | auto=True 32 | battery=False 33 | hvwarning=False 34 | dc=True 35 | peak_max=False 36 | peak_min=False 37 | ``` 38 | -------------------------------------------------------------------------------- /from_vendor/README.md: -------------------------------------------------------------------------------- 1 | these files are from the decompiled android app - licence is not GPL :) 2 | -------------------------------------------------------------------------------- /from_vendor/TestDataModel.java: -------------------------------------------------------------------------------- 1 | package com.uni_t.multimeter.bean; 2 | 3 | import android.util.Log; 4 | import androidx.exifinterface.media.ExifInterface; 5 | import com.uni_t.multimeter.C1757R; 6 | import com.uni_t.multimeter.utils.FileUtils; 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | public class TestDataModel { 12 | public static final int FUNCTION_ACA = 17; 13 | public static final int FUNCTION_ACA2 = 22; 14 | public static final int FUNCTION_ACDC = 25; 15 | public static final int FUNCTION_ACV = 0; 16 | public static final int FUNCTION_AC_DC = 28; 17 | public static final int FUNCTION_AC_DC3 = 30; 18 | public static final int FUNCTION_ACmA = 15; 19 | public static final int FUNCTION_ACmV = 1; 20 | public static final int FUNCTION_ACuA = 13; 21 | public static final int FUNCTION_CAP = 9; 22 | public static final int FUNCTION_CONT = 7; 23 | public static final int FUNCTION_DCA = 16; 24 | public static final int FUNCTION_DCA2 = 23; 25 | public static final int FUNCTION_DCV = 2; 26 | public static final int FUNCTION_DCmA = 14; 27 | public static final int FUNCTION_DCmV = 3; 28 | public static final int FUNCTION_DCuA = 12; 29 | public static final int FUNCTION_DIDOE = 8; 30 | public static final int FUNCTION_HFE = 18; 31 | public static final int FUNCTION_Hz = 4; 32 | public static final int FUNCTION_INRUSH = 31; 33 | public static final int FUNCTION_LPF = 24; 34 | public static final int FUNCTION_LPF2 = 27; 35 | public static final int FUNCTION_LPF3 = 29; 36 | public static final int FUNCTION_Live = 19; 37 | public static final int FUNCTION_LozV = 21; 38 | public static final int FUNCTION_NCV = 20; 39 | public static final int FUNCTION_OHM = 6; 40 | public static final int FUNCTION_Per = 5; 41 | public static final int FUNCTION_TC = 10; 42 | public static final int FUNCTION_TF = 11; 43 | private static final String[] functionStrings = {"ACV", "ACmV", "DCV", "DCmV", "Hz", "%", "OHM", "CONT", "DIDOE", "CAP", "°C", "°F", "DCuA", "ACuA", "DCmA", "ACmA", "DCA", "ACA", "HFE", "Live", "NCV", "LozV", "ACA", "DCA", "LPF", "AC/DC", "LPF", "AC+DC", "LPF", "AC+DC2", "INRUSH", ""}; 44 | private static final String maxOLString = ".OL,O.L,OL.,OL"; 45 | private static final String minOLString = "-.OL,-O.L,-OL.,-OL"; 46 | private static JSONObject olJson = null; 47 | private String devMac; 48 | private String function; 49 | private boolean isAuto; 50 | private boolean isBarPol; 51 | private boolean isBattery; 52 | private boolean isDc; 53 | private boolean isHold; 54 | private boolean isHvWarning; 55 | private boolean isMax; 56 | private boolean isMin; 57 | private boolean isPeakMax; 58 | private boolean isPeakMin; 59 | private boolean isRel; 60 | private String liangcheng; 61 | private int loadProgress; 62 | private int maxShow; 63 | private float maxValue; 64 | private float minValue; 65 | private String pairName; 66 | private String showID; 67 | private int status; 68 | private String typeName; 69 | private String unitValue; 70 | private String value; 71 | 72 | public boolean isMaxOlValue() { 73 | String str = this.value; 74 | if (str != null) { 75 | return maxOLString.contains(str); 76 | } 77 | return false; 78 | } 79 | 80 | public boolean isMinOlValue() { 81 | String str = this.value; 82 | if (str == null || !str.contains("-") || !minOLString.contains(this.value)) { 83 | return false; 84 | } 85 | return true; 86 | } 87 | 88 | public String getShowID() { 89 | String str = this.showID; 90 | return str == null ? "" : str; 91 | } 92 | 93 | public void setShowID(String str) { 94 | this.showID = str; 95 | } 96 | 97 | public int getmaxShow() { 98 | if (this.isMax) { 99 | return C1757R.mipmap.ic_flag_max; 100 | } 101 | if (this.isMin) { 102 | return C1757R.mipmap.ic_flag_min; 103 | } 104 | if (this.isPeakMax) { 105 | return C1757R.mipmap.ic_flag_pmax; 106 | } 107 | if (this.isPeakMin) { 108 | return C1757R.mipmap.ic_flag_pmin; 109 | } 110 | return 0; 111 | } 112 | 113 | public float getMaxValue() { 114 | return this.maxValue; 115 | } 116 | 117 | public void setMaxValue(float f) { 118 | this.maxValue = f; 119 | } 120 | 121 | public float getMinValue() { 122 | return this.minValue; 123 | } 124 | 125 | public void setMinValue(float f) { 126 | this.minValue = f; 127 | } 128 | 129 | public void anylseData(byte[] bArr) { 130 | JSONObject optJSONObject; 131 | if (bArr != null && bArr.length > 3) { 132 | boolean z = false; 133 | if (bArr[0] == -85 && bArr[1] == -51 && bArr[2] == bArr.length - 3) { 134 | setFunction(getFunctionString(bArr[3])); 135 | JSONObject jSONObject = olJson; 136 | if (!(jSONObject == null || (optJSONObject = jSONObject.optJSONObject(getFunction())) == null)) { 137 | StringBuilder sb = new StringBuilder(); 138 | sb.append(bArr[4] - 48); 139 | sb.append(""); 140 | JSONArray optJSONArray = optJSONObject.optJSONArray(sb.toString()); 141 | if (optJSONArray == null) { 142 | optJSONArray = optJSONObject.optJSONArray((optJSONObject.length() - 1) + ""); 143 | } 144 | if (optJSONArray != null && optJSONArray.length() == 4) { 145 | setLiangcheng(optJSONArray.optString(0)); 146 | setUnitValue(optJSONArray.optString(1)); 147 | this.maxValue = (float) optJSONArray.optDouble(2); 148 | this.minValue = (float) optJSONArray.optDouble(3); 149 | } 150 | } 151 | setValue(new String(new byte[]{bArr[5], bArr[6], bArr[7], bArr[8], bArr[9], bArr[10], bArr[11]})); 152 | this.loadProgress = (bArr[12] * 10) + bArr[13]; 153 | this.isMax = (bArr[14] & 8) != 0; 154 | this.isMin = (bArr[14] & 4) != 0; 155 | this.isHold = (bArr[14] & 2) != 0; 156 | this.isRel = (bArr[14] & 1) != 0; 157 | this.isAuto = (bArr[15] & 4) == 0; 158 | this.isBattery = (bArr[15] & 2) != 0; 159 | this.isHvWarning = (bArr[15] & 1) != 0; 160 | this.isDc = (bArr[16] & 8) == 0; 161 | this.isPeakMax = (bArr[16] & 4) != 0; 162 | this.isPeakMin = (2 & bArr[16]) != 0; 163 | if ((bArr[16] & 1) != 0) { 164 | z = true; 165 | } 166 | this.isBarPol = z; 167 | } 168 | } 169 | } 170 | 171 | public static TestDataModel getInstanceFromBytes(byte[] bArr) { 172 | TestDataModel testDataModel = new TestDataModel(); 173 | testDataModel.anylseData(bArr); 174 | return testDataModel; 175 | } 176 | 177 | private static String getFunctionString(int i) { 178 | if (i >= 0) { 179 | String[] strArr = functionStrings; 180 | if (i < strArr.length) { 181 | return strArr[i]; 182 | } 183 | } 184 | String[] strArr2 = functionStrings; 185 | return strArr2[strArr2.length - 1]; 186 | } 187 | 188 | public TestDataModel(String str, String str2) { 189 | this.pairName = str; 190 | this.devMac = str2; 191 | try { 192 | olJson = FileUtils.INSTANCE.readJsonFromAsset("funOl.json").getJSONObject("OL"); 193 | } catch (JSONException e) { 194 | e.printStackTrace(); 195 | } 196 | } 197 | 198 | public TestDataModel() { 199 | try { 200 | olJson = FileUtils.INSTANCE.readJsonFromAsset("funOl.json").getJSONObject("OL"); 201 | } catch (JSONException e) { 202 | e.printStackTrace(); 203 | } 204 | } 205 | 206 | public String getFunction() { 207 | return this.function; 208 | } 209 | 210 | public void setFunction(String str) { 211 | this.function = str; 212 | } 213 | 214 | public String getLiangcheng() { 215 | return this.liangcheng; 216 | } 217 | 218 | public String getOLCheckLianchang() { 219 | if ((!this.isPeakMin && !this.isPeakMax) || (!"ACmV".equals(this.function) && !"ACV".equals(this.function) && !"ACA".equals(this.function) && !"ACuA".equals(this.function) && !"ACmA".equals(this.function))) { 220 | return this.liangcheng; 221 | } 222 | if (("1000V".equals(this.liangcheng) || "20A".equals(this.liangcheng)) && this.typeName.contains(ExifInterface.LONGITUDE_EAST)) { 223 | String str = "e_" + this.function.toLowerCase() + "_peak_" + this.liangcheng; 224 | Log.e("TestDataModel", str); 225 | return str; 226 | } 227 | Log.e("TestDataModel", this.function.toLowerCase() + "_peak_" + this.liangcheng); 228 | return this.function.toLowerCase() + "_peak_" + this.liangcheng; 229 | } 230 | 231 | public void setLiangcheng(String str) { 232 | this.liangcheng = str; 233 | } 234 | 235 | public String getValue() { 236 | return this.value; 237 | } 238 | 239 | public float getValueNumber() { 240 | try { 241 | if (this.value != null) { 242 | return Float.parseFloat(this.value.trim()); 243 | } 244 | return 0.0f; 245 | } catch (Exception unused) { 246 | return 0.0f; 247 | } 248 | } 249 | 250 | public void setValue(String str) { 251 | this.value = str.replaceAll(" ", ""); 252 | } 253 | 254 | public int getLoadProgress() { 255 | return this.loadProgress; 256 | } 257 | 258 | public void setLoadProgress(int i) { 259 | this.loadProgress = i; 260 | } 261 | 262 | public boolean isMax() { 263 | return this.isMax; 264 | } 265 | 266 | public void setMax(boolean z) { 267 | this.isMax = z; 268 | } 269 | 270 | public boolean isMin() { 271 | return this.isMin; 272 | } 273 | 274 | public void setMin(boolean z) { 275 | this.isMin = z; 276 | } 277 | 278 | public boolean isHold() { 279 | return this.isHold; 280 | } 281 | 282 | public boolean isPer() { 283 | return "%".equals(getFunction()); 284 | } 285 | 286 | public void setHold(boolean z) { 287 | this.isHold = z; 288 | } 289 | 290 | public boolean isRel() { 291 | return this.isRel; 292 | } 293 | 294 | public void setRel(boolean z) { 295 | this.isRel = z; 296 | } 297 | 298 | public boolean isAuto() { 299 | return this.isAuto; 300 | } 301 | 302 | public void setAuto(boolean z) { 303 | this.isAuto = z; 304 | } 305 | 306 | public boolean isBattery() { 307 | return this.isBattery; 308 | } 309 | 310 | public void setBattery(boolean z) { 311 | this.isBattery = z; 312 | } 313 | 314 | public boolean isHvWarning() { 315 | return this.isHvWarning; 316 | } 317 | 318 | public void setHvWarning(boolean z) { 319 | this.isHvWarning = z; 320 | } 321 | 322 | public boolean isDc() { 323 | return this.isDc; 324 | } 325 | 326 | public void setDc(boolean z) { 327 | this.isDc = z; 328 | } 329 | 330 | public boolean isPeakMax() { 331 | return this.isPeakMax; 332 | } 333 | 334 | public void setPeakMax(boolean z) { 335 | this.isPeakMax = z; 336 | } 337 | 338 | public boolean isPeakMin() { 339 | return this.isPeakMin; 340 | } 341 | 342 | public void setPeakMin(boolean z) { 343 | this.isPeakMin = z; 344 | } 345 | 346 | public boolean isBarPol() { 347 | return this.isBarPol; 348 | } 349 | 350 | public void setBarPol(boolean z) { 351 | this.isBarPol = z; 352 | } 353 | 354 | public String getPairName() { 355 | return this.pairName; 356 | } 357 | 358 | public void setPairName(String str) { 359 | this.pairName = str; 360 | } 361 | 362 | public String getTypeName() { 363 | String str = this.typeName; 364 | if (str == null || str.isEmpty()) { 365 | return this.pairName; 366 | } 367 | return this.typeName; 368 | } 369 | 370 | public void setTypeName(String str) { 371 | JSONObject optJSONObject; 372 | this.typeName = str; 373 | FileUtils fileUtils = FileUtils.INSTANCE; 374 | JSONObject readJsonFromAsset = fileUtils.readJsonFromAsset("funOl_" + str + ".json"); 375 | if (readJsonFromAsset == null) { 376 | readJsonFromAsset = FileUtils.INSTANCE.readJsonFromAsset("funOl.json"); 377 | } 378 | if (readJsonFromAsset != null && (optJSONObject = readJsonFromAsset.optJSONObject("OL")) != null) { 379 | olJson = optJSONObject; 380 | } 381 | } 382 | 383 | public String getDevMac() { 384 | return this.devMac; 385 | } 386 | 387 | public void setDevMac(String str) { 388 | this.devMac = str; 389 | } 390 | 391 | public int getStatus() { 392 | return this.status; 393 | } 394 | 395 | public void setStatus(int i) { 396 | this.status = i; 397 | } 398 | 399 | public String getUnitValue() { 400 | return this.unitValue; 401 | } 402 | 403 | public void setUnitValue(String str) { 404 | this.unitValue = str; 405 | } 406 | 407 | public String toString() { 408 | return "TestDataModel{function='" + this.function + '\'' + ", liangcheng='" + this.liangcheng + '\'' + ", value='" + this.value + '\'' + ", loadProgress=" + this.loadProgress + ", isMax=" + this.isMax + ", isMin=" + this.isMin + ", isHold=" + this.isHold + ", isRel=" + this.isRel + ", isAuto=" + this.isAuto + ", isBattery=" + this.isBattery + ", isHvWarning=" + this.isHvWarning + ", isDc=" + this.isDc + ", isPeakMax=" + this.isPeakMax + ", isPeakMin=" + this.isPeakMin + ", isBarPol=" + this.isBarPol + ", pairName='" + this.pairName + '\'' + ", typeName='" + this.typeName + '\'' + ", devMac='" + this.devMac + '\'' + ", status=" + this.status + '}'; 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /from_vendor/anjian_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "anjian": { 3 | "UT161D": { 4 | "LozV": {"selectPic": 1, "selectFlag": false, "range": true, "hzp": true, "rel": false, "maxmin": true, "hold": true, "maxminPic": 1}, 5 | "ACV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 6 | "DCV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 7 | "ACmV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 8 | "DCmV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 9 | "OHM": {"selectPic": 3, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 10 | "CONT": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 11 | "DIDOE": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 12 | "CAP": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 13 | "Hz": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 14 | "%": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 15 | "°C": {"selectPic": 5, "selectFlag": true, "range": true, "hzp": false, "rel": false, "maxmin": true, "hold": true, "maxminPic": 1}, 16 | "°F": {"selectPic": 5, "selectFlag": true, "range": true, "hzp": false, "rel": false, "maxmin": true, "hold": true, "maxminPic": 1}, 17 | "DCuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 18 | "ACuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 19 | "DCmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 20 | "ACmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 21 | "DCA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 22 | "ACA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 23 | "NCV": {"selectPic": 1, "selectFlag": false, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": false, "maxminPic": 1} 24 | }, 25 | "UT161B": { 26 | "ACV": {"selectPic": 1, "selectFlag": false, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 27 | "DCV": {"selectPic": 1, "selectFlag": false, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 28 | "ACmV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 29 | "DCmV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 30 | "OHM": {"selectPic": 7, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 31 | "CONT": {"selectPic": 7, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 32 | "DIDOE": {"selectPic": 8, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 33 | "CAP": {"selectPic": 8, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 34 | "Hz": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 35 | "%": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 36 | "DCuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 37 | "ACuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 38 | "DCmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 39 | "ACmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 40 | "DCA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 41 | "ACA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 42 | "NCV": {"selectPic": 1, "selectFlag": false, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": false, "maxminPic": 1} 43 | }, 44 | "UT161E": { 45 | "ACV": {"selectPic": 9, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 46 | "LPF": {"selectPic": 9, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 47 | "DCV": {"selectPic": 10, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 48 | "AC/DC": {"selectPic": 10, "selectFlag": true, "range": true, "hzp": false, "rel": false, "maxmin": true, "hold": true, "maxminPic": 1}, 49 | "ACmV": {"selectPic": 2, "selectFlag": true, "range": false, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 50 | "DCmV": {"selectPic": 2, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 51 | "OHM": {"selectPic": 3, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 52 | "CONT": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 53 | "DIDOE": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 54 | "CAP": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 55 | "Hz": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 56 | "%": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 57 | "HFE": {"selectPic": 1, "selectFlag": false, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 58 | "DCuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 59 | "ACuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 60 | "DCmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 61 | "ACmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 62 | "DCA": {"selectPic": 6, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 63 | "ACA": {"selectPic": 6, "selectFlag": true, "range": false, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 64 | "NCV": {"selectPic": 1, "selectFlag": false, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": false, "maxminPic": 1} 65 | }, 66 | "UT61D+": { 67 | "LozV": {"selectPic": 1, "selectFlag": false, "range": true, "hzp": true, "rel": false, "maxmin": true, "hold": true, "maxminPic": 1}, 68 | "ACV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 69 | "DCV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 70 | "ACmV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 71 | "DCmV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 72 | "OHM": {"selectPic": 3, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 73 | "CONT": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 74 | "DIDOE": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 75 | "CAP": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 76 | "Hz": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 77 | "%": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 78 | "°C": {"selectPic": 5, "selectFlag": true, "range": true, "hzp": false, "rel": false, "maxmin": true, "hold": true, "maxminPic": 1}, 79 | "°F": {"selectPic": 5, "selectFlag": true, "range": true, "hzp": false, "rel": false, "maxmin": true, "hold": true, "maxminPic": 1}, 80 | "DCuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 81 | "ACuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 82 | "DCmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 83 | "ACmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 84 | "DCA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 85 | "ACA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 86 | "NCV": {"selectPic": 1, "selectFlag": false, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": false, "maxminPic": 1} 87 | }, 88 | "UT61B+": { 89 | "ACV": {"selectPic": 1, "selectFlag": false, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 90 | "DCV": {"selectPic": 1, "selectFlag": false, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 91 | "ACmV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 92 | "DCmV": {"selectPic": 2, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 93 | "OHM": {"selectPic": 7, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 94 | "CONT": {"selectPic": 7, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 95 | "DIDOE": {"selectPic": 8, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 96 | "CAP": {"selectPic": 8, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 97 | "Hz": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 98 | "%": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 99 | "DCuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 100 | "ACuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 101 | "DCmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 102 | "ACmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 103 | "DCA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 104 | "ACA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 105 | "NCV": {"selectPic": 1, "selectFlag": false, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": false, "maxminPic": 1} 106 | }, 107 | "UT61E+": { 108 | "ACV": {"selectPic": 9, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 109 | "LPF": {"selectPic": 9, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 110 | "DCV": {"selectPic": 10, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 111 | "AC/DC": {"selectPic": 10, "selectFlag": true, "range": true, "hzp": false, "rel": false, "maxmin": true, "hold": true, "maxminPic": 1}, 112 | "ACmV": {"selectPic": 2, "selectFlag": true, "range": false, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 2}, 113 | "DCmV": {"selectPic": 2, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 114 | "OHM": {"selectPic": 3, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 115 | "CONT": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 116 | "DIDOE": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 117 | "CAP": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 118 | "Hz": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 119 | "%": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": true, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 120 | "HFE": {"selectPic": 1, "selectFlag": false, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 121 | "DCuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 122 | "ACuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 123 | "DCmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 124 | "ACmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 125 | "DCA": {"selectPic": 6, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 126 | "ACA": {"selectPic": 6, "selectFlag": true, "range": false, "hzp": true, "rel": true, "maxmin": true, "hold": true, "maxminPic": 1}, 127 | "NCV": {"selectPic": 1, "selectFlag": false, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": false, "maxminPic": 1} 128 | }, 129 | "UT60BT": { 130 | "ACV": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 131 | "DCV": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 132 | "ACmV": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 133 | "DCmV": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 134 | "OHM": {"selectPic": 3, "selectFlag": true, "range": true, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 135 | "CONT": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 136 | "DIDOE": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 137 | "CAP": {"selectPic": 3, "selectFlag": true, "range": false, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 138 | "Hz": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 139 | "%": {"selectPic": 4, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 140 | "°C": {"selectPic": 5, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 141 | "°F": {"selectPic": 5, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": true, "maxminPic": 1}, 142 | "DCuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 143 | "ACuA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 144 | "DCmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 145 | "ACmA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 146 | "DCA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 147 | "ACA": {"selectPic": 6, "selectFlag": true, "range": true, "hzp": false, "rel": true, "maxmin": false, "hold": true, "maxminPic": 1}, 148 | "NCV": {"selectPic": 11, "selectFlag": true, "range": false, "hzp": false, "rel": false, "maxmin": false, "hold": false, "maxminPic": 1} 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /from_vendor/funOl_UT161B.json: -------------------------------------------------------------------------------- 1 | { 2 | "OL": { 3 | "DCV": {"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 4 | "ACV":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 5 | "LPF":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 6 | "LozV":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 7 | "AC/DC":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 8 | "DCmV":{"0":["60mV", "mV", "62", "-62"], "1":["600mV", "mV", "620", "-620"]}, 9 | "ACmV":{"0":["60mV", "mV", "62", "-62"], "1":["600mV", "mV", "620", "-620"]}, 10 | "OHM":{"0":["600Ω", "Ω", "620", "-620"], "1":["6kΩ", "kΩ", "6.2", "-6.2"], "2":["60kΩ", "kΩ", "62", "-62"], "3":["600kΩ", "kΩ", "620", "-620"], "4":["6MΩ", "MΩ", "6.2", "-6.2"], "5":["60MΩ", "MΩ", "62", "-62"]}, 11 | "CAP":{"0":["60nF", "nF", "62", "-62"], "1":["600nF", "nF", "620", "-620"], "2":["6uF", "uF", "6.2", "-6.2"], "3":["60uF", "uF", "62", "-62"], "4":["600uF", "uF", "620", "-620"], "5":["6mF", "mF", "6.2", "-6.2"], "6":["60mF", "mF", "62", "-62"]}, 12 | "DCuA":{"0":["600uA", "uA", "620", "-620"], "1":["6000uA", "uA", "6200", "-6200"]}, 13 | "DCmA":{"0":["60mA", "mA", "62", "-62"], "1":["600mA", "mA", "620", "-620"]}, 14 | "DCA":{"0":["6A", "A", "6.2", "-6.2"], "1":["10A", "A", "10.1", "-10.1"]}, 15 | "ACuA":{ "0":["600uA", "uA", "620", "-620"], "1":["6000uA", "uA", "6200", "-6200"]}, 16 | "ACmA":{"0":["60mA", "mA", "62", "-62"], "1":["600mA", "mA", "620", "-620"]}, 17 | "ACA":{"0":["6A", "A", "6.2", "-6.2"], "1":["10A", "A", "10.1", "-10.1"]}, 18 | "AC+DC":{"0":["6A", "A", "6.2", "-6.2"], "1":["10A", "A", "10.1", "-10.1"]}, 19 | "AC+DC2":{"0":["6A", "A", "6.2", "-6.2"], "1":["10A", "A", "10.1", "-10.1"]}, 20 | "Hz":{"0":["99.99Hz", "Hz", "100", "-100"], "1":["999.9Hz", "Hz", "1000", "-1000"], "2":["9.999kHz", "kHz", "10", "-10"], "3":["99.99kHz", "kHz", "100", "-100"], "4":["999.9kHz", "kHz", "1000", "-1000"], "5":["9.999mHz", "MHz", "10", "-10"]}, 21 | "°C":{"0":["-40°C~300°C", "°C", "310", "-50"], "1":["300°C~1000°C", "°C", "1020", "-50"]}, 22 | "°F":{"0":["-40°F~572°F", "°F", "590", "-58"], "1":["572°F~1832°F", "°F", "1868", "-58"]}, 23 | "DIDOE": {"0": ["3V", "V", "3.1", "-3.1"]}, 24 | "CONT": {"0": ["600Ω", "Ω", "620", "-620"]} 25 | } 26 | } -------------------------------------------------------------------------------- /from_vendor/funOl_UT161D.json: -------------------------------------------------------------------------------- 1 | { 2 | "OL": { 3 | "DCV": {"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 4 | "ACV":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 5 | "LPF":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 6 | "LozV":{"0":["600V", "V", "620", "-620"], "1":["1000V", "V", "1010", "-1010"]}, 7 | "AC/DC":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 8 | "DCmV":{"0":["60mV", "mV", "62", "-62"], "1":["600mV", "mV", "620", "-620"]}, 9 | "ACmV":{"0":["60mV", "mV", "62", "-62"], "1":["600mV", "mV", "620", "-620"]}, 10 | "OHM":{"0":["600Ω", "Ω", "620", "-620"], "1":["6kΩ", "kΩ", "6.2", "-6.2"], "2":["60kΩ", "kΩ", "62", "-62"], "3":["600kΩ", "kΩ", "620", "-620"], "4":["6MΩ", "MΩ", "6.2", "-6.2"], "5":["60MΩ", "MΩ", "62", "-62"]}, 11 | "CAP":{"0":["60nF", "nF", "62", "-62"], "1":["600nF", "nF", "620", "-620"], "2":["6uF", "uF", "6.2", "-6.2"], "3":["60uF", "uF", "62", "-62"], "4":["600uF", "uF", "620", "-620"], "5":["6mF", "mF", "6.2", "-6.2"], "6":["60mF", "mF", "62", "-62"]}, 12 | "DCuA":{"0":["600uA", "uA", "620", "-620"], "1":["6000uA", "uA", "6200", "-6200"]}, 13 | "DCmA":{"0":["60mA", "mA", "62", "-62"], "1":["600mA", "mA", "620", "-620"]}, 14 | "DCA":{"0":["6A", "A", "6.2", "-6.2"], "1":["20A", "A", "20.1", "-20.1"]}, 15 | "ACuA":{ "0":["600uA", "uA", "620", "-620"], "1":["6000uA", "uA", "6200", "-6200"]}, 16 | "ACmA":{"0":["60mA", "mA", "62", "-62"], "1":["600mA", "mA", "620", "-620"]}, 17 | "ACA":{"0":["6A", "A", "6.2", "-6.2"], "1":["20A", "A", "20.1", "-20.1"]}, 18 | "AC+DC":{"0":["6A", "A", "6.2", "-6.2"], "1":["20A", "A", "20.1", "-20.1"]}, 19 | "AC+DC2":{"0":["6A", "A", "6.2", "-6.2"], "1":["20A", "A", "20.1", "-20.1"]}, 20 | "Hz":{"0":["99.99Hz", "Hz", "100", "-100"], "1":["999.9Hz", "Hz", "1000", "-1000"], "2":["9.999kHz", "kHz", "10", "-10"], "3":["99.99kHz", "kHz", "100", "-100"], "4":["999.9kHz", "kHz", "1000", "-1000"], "5":["9.999mHz", "MHz", "10", "-10"]}, 21 | "°C":{"0":["-40°C~300°C", "°C", "310", "-50"], "1":["300°C~1000°C", "°C", "1020", "-50"]}, 22 | "°F":{"0":["-40°F~572°F", "°F", "590", "-58"], "1":["572°F~1832°F", "°F", "1868", "-58"]}, 23 | "DIDOE": {"0": ["3V", "V", "3.1", "-3.1"]}, 24 | "CONT": {"0": ["600Ω", "Ω", "620", "-620"]} 25 | } 26 | } -------------------------------------------------------------------------------- /from_vendor/funOl_UT161E.json: -------------------------------------------------------------------------------- 1 | { 2 | "OL": { 3 | "DCV": {"0":["2.2V", "V", "2.3", "-2.3"], "1":["22V", "V", "23", "-23"], "2":["220V", "V", "230", "-230"], "3":["1000V", "V", "1010", "-1010"]}, 4 | "ACV":{"0":["2.2V", "V", "2.3", "-2.3"], "1":["22V", "V", "23", "-23"], "2":["220V", "V", "230", "-230"], "3":["1000V", "V", "1010", "-1010"]}, 5 | "LPF":{"0":["2.2V", "V", "2.3", "-2.3"], "1":["22V", "V", "23", "-23"], "2":["220V", "V", "230", "-230"], "3":["1000V", "V", "1010", "-1010"]}, 6 | "LozV":{"0":["2.2V", "V", "2.3", "-2.3"], "1":["22V", "V", "23", "-23"], "2":["220V", "V", "230", "-230"], "3":["1000V", "V", "1010", "-1010"]}, 7 | "DCmV":{"0":["220mV", "mV", "230", "-230"]}, 8 | "ACmV":{"0":["220mV", "mV", "230", "-230"]}, 9 | "OHM":{"0":["220Ω", "Ω", "230", "-230"], "1":["2.2kΩ", "kΩ", "2.3", "-2.3"], "2":["22kΩ", "kΩ", "23", "-23"], "3":["220kΩ", "kΩ", "230", "-230"], "4":["2.2MΩ", "MΩ", "2.3", "-2.3"], "5":["22MΩ", "MΩ", "23", "-23"], "6":["220MΩ", "MΩ", "230", "-230"]}, 10 | "CAP":{"0":["22nF", "nF", "23", "-23"], "1":["220nF", "nF", "230", "-230"], "2":["2.2uF", "uF", "2.3", "-2.3"], "3":["22uF", "uF", "23", "-23"], "4":["230uF", "uF", "230", "-230"], "5":["2.2mF", "mF", "2.3", "-2.3"], "6":["22mF", "mF", "23", "-23"], "7":["220mF", "mF", "230", "-230"]}, 11 | "DCuA":{"0":["220uA", "uA", "230", "-230"], "1":["2200uA", "uA", "2300", "-2300"]}, 12 | "DCmA":{"0":["22mA", "mA", "23", "-23"], "1":["220mA", "mA", "230", "-230"]}, 13 | "DCA":{"1":["20A", "A", "20.1", "-20.1"]}, 14 | "ACuA":{"0":["220uA", "uA", "230", "-230"], "1":["2200uA", "uA", "2300", "-2300"]}, 15 | "ACmA":{"0":["22mA", "mA", "23", "-23"], "1":["220mA", "mA", "230", "-230"]}, 16 | "ACA":{"1":["20A", "A", "20.1", "-20.1"]}, 17 | "AC+DC":{"1":["20A", "A", "20.1", "-20.1"]}, 18 | "AC+DC2":{"1":["20A", "A", "20.1", "-20.1"]}, 19 | "Hz":{"0":["22Hz", "Hz", "23", "-23"], "1":["220Hz", "Hz", "230", "-230"], "2":["2.2kHz", "kHz", "2.3", "-2.3"], "3":["22kHz", "kHz", "23", "-23"], "4":["220kHz", "kHz", "230", "-230"], "5":["2.2mHz", "MHz", "2.3", "-2.3"], "6":["22mHz", "MHz", "23", "-23"], "7":["220mHz", "MHz", "230", "-230"]}, 20 | "°C":{"0":["-40°C~300°C", "°C", "310", "-50"], "1":["300°C~1000°C", "°C", "1020", "-50"]}, 21 | "°F":{"0":["-40°F~572°F", "°F", "590", "-58"], "1":["572°F~1832°F", "°F", "1868", "-58"]}, 22 | "AC/DC":{"0":["2.2V", "V", "2.3", "-2.3"], "1":["22V", "V", "23", "-23"], "2":["220V", "V", "230", "-230"], "3":["1000V", "V", "1010", "-1010"]}, 23 | "DIDOE": {"0": ["3.0V", "V", "2.3", "-2.3"]}, 24 | "CONT": {"0": ["220Ω", "Ω", "230", "-230"]}, 25 | "%": {"0": ["100%", "%", "110", "-110"]} 26 | } 27 | } -------------------------------------------------------------------------------- /from_vendor/funOl_UT60BT.json: -------------------------------------------------------------------------------- 1 | { 2 | "OL": { 3 | "DCV": {"0":["999.9mV", "mV", "1000", "-1000"], "1":["9.999V", "V", "10", "-10"], "2":["99.99V", "V", "100", "-100"], "3":["999.9V", "V", "1000", "-1000"]}, 4 | "ACV":{"0":["999.9mV", "mV", "1000", "-1000"], "1":["9.99V", "V", "10", "-10"], "2":["99.99V", "V", "100", "-100"], "3":["999.9V", "V", "1000", "-1000"]}, 5 | "DCmV":{"0":["9.999mV", "mV", "10", "-10"], "1":["99.99mV", "mV", "100", "-100"]}, 6 | "ACmV":{"0":["9.999mV", "mV", "10", "-10"], "1":["99.99mV", "mV", "100", "-100"]}, 7 | "OHM":{"0":["999.9Ω", "Ω", "230", "-230"], "1":["9.999kΩ", "kΩ", "2.3", "-2.3"], "2":["99.99kΩ", "kΩ", "23", "-23"], "3":["999.9kΩ", "kΩ", "230", "-230"], "4":["9.99MΩ", "MΩ", "2.3", "-2.3"], "5":["99.99MΩ", "MΩ", "23", "-23"]}, 8 | "CAP":{"0":["9.999nF", "nF", "23", "-23"], "1":["99.99nF", "nF", "230", "-230"], "2":["999.9nF", "nF", "2.3", "-2.3"], "3":["9.999uF", "uF", "23", "-23"], "4":["99.99uF", "uF", "230", "-230"], "5":["999.9uF", "uF", "2.3", "-2.3"], "6":["9.999mF", "mF", "23", "-23"], "7":["99.99mF", "mF", "230", "-230"]}, 9 | "DCuA":{"0":["999.9uA", "uA", "230", "-230"]}, 10 | "DCmA":{"0":["999.9mA", "mA", "23", "-23"], "1":["9.999A", "A", "230", "-230"]}, 11 | "ACuA":{"0":["999.9uA", "uA", "230", "-230"]}, 12 | "ACmA":{"0":["999.9mA", "mA", "23", "-23"], "1":["9.999A", "A", "230", "-230"]}, 13 | "Hz":{"0":["9.9990Hz", "Hz", "23", "-23"], "1":["99.990Hz", "Hz", "230", "-230"], "2":["999.90Hz", "Hz", "2.3", "-2.3"], "3":["9.9990kHz", "kHz", "23", "-23"], "4":["99.990kHz", "kHz", "230", "-230"], "5":["999.90kHz", "kHz", "2.3", "-2.3"], "6":["9.9990mHz", "MHz", "23", "-23"], "7":["99.990mHz", "MHz", "230", "-230"]}, 14 | "°C":{"0":["-40°C~1000°C", "°C", "310", "-50"]}, 15 | "°F":{"0":["-40°F~1832°F", "°F", "590", "-58"]}, 16 | "DIDOE": {"0": ["3.0V", "V", "2.3", "-2.3"]}, 17 | "CONT": {"0": ["220Ω", "Ω", "230", "-230"]}, 18 | "%": {"0": ["100%", "%", "110", "-110"]} 19 | } 20 | } -------------------------------------------------------------------------------- /from_vendor/funOl_UT61B+.json: -------------------------------------------------------------------------------- 1 | { 2 | "OL": { 3 | "DCV": {"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 4 | "ACV":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 5 | "LPF":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 6 | "LozV":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 7 | "AC/DC":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 8 | "DCmV":{"0":["60mV", "mV", "62", "-62"], "1":["600mV", "mV", "620", "-620"]}, 9 | "ACmV":{"0":["60mV", "mV", "62", "-62"], "1":["600mV", "mV", "620", "-620"]}, 10 | "OHM":{"0":["600Ω", "Ω", "620", "-620"], "1":["6kΩ", "kΩ", "6.2", "-6.2"], "2":["60kΩ", "kΩ", "62", "-62"], "3":["600kΩ", "kΩ", "620", "-620"], "4":["6MΩ", "MΩ", "6.2", "-6.2"], "5":["60MΩ", "MΩ", "62", "-62"]}, 11 | "CAP":{"0":["60nF", "nF", "62", "-62"], "1":["600nF", "nF", "620", "-620"], "2":["6uF", "uF", "6.2", "-6.2"], "3":["60uF", "uF", "62", "-62"], "4":["600uF", "uF", "620", "-620"], "5":["6mF", "mF", "6.2", "-6.2"], "6":["60mF", "mF", "62", "-62"]}, 12 | "DCuA":{"0":["600uA", "uA", "620", "-620"], "1":["6000uA", "uA", "6200", "-6200"]}, 13 | "DCmA":{"0":["60mA", "mA", "62", "-62"], "1":["600mA", "mA", "620", "-620"]}, 14 | "DCA":{"0":["6A", "A", "6.2", "-6.2"], "1":["10A", "A", "10.1", "-10.1"]}, 15 | "ACuA":{ "0":["600uA", "uA", "620", "-620"], "1":["6000uA", "uA", "6200", "-6200"]}, 16 | "ACmA":{"0":["60mA", "mA", "62", "-62"], "1":["600mA", "mA", "620", "-620"]}, 17 | "ACA":{"0":["6A", "A", "6.2", "-6.2"], "1":["10A", "A", "10.1", "-10.1"]}, 18 | "AC+DC":{"0":["6A", "A", "6.2", "-6.2"], "1":["10A", "A", "10.1", "-10.1"]}, 19 | "AC+DC2":{"0":["6A", "A", "6.2", "-6.2"], "1":["10A", "A", "10.1", "-10.1"]}, 20 | "Hz":{"0":["99.99Hz", "Hz", "100", "-100"], "1":["999.9Hz", "Hz", "1000", "-1000"], "2":["9.999kHz", "kHz", "10", "-10"], "3":["99.99kHz", "kHz", "100", "-100"], "4":["999.9kHz", "kHz", "1000", "-1000"], "5":["9.999mHz", "MHz", "10", "-10"]}, 21 | "°C":{"0":["-40°C~300°C", "°C", "310", "-50"], "1":["300°C~1000°C", "°C", "1020", "-50"]}, 22 | "°F":{"0":["-40°F~572°F", "°F", "590", "-58"], "1":["572°F~1832°F", "°F", "1868", "-58"]}, 23 | "DIDOE": {"0": ["3V", "V", "3.1", "-3.1"]}, 24 | "CONT": {"0": ["600Ω", "Ω", "620", "-620"]} 25 | } 26 | } -------------------------------------------------------------------------------- /from_vendor/funOl_UT61D+.json: -------------------------------------------------------------------------------- 1 | { 2 | "OL": { 3 | "DCV": {"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 4 | "ACV":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 5 | "LPF":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 6 | "LozV":{"0":["600V", "V", "620", "-620"], "1":["1000V", "V", "1010", "-1010"]}, 7 | "AC/DC":{"0":["6V", "V", "6.2", "-6.2"], "1":["60V", "V", "62", "-62"], "2":["600V", "V", "620", "-620"], "3":["1000V", "V", "1010", "-1010"]}, 8 | "DCmV":{"0":["60mV", "mV", "62", "-62"], "1":["600mV", "mV", "620", "-620"]}, 9 | "ACmV":{"0":["60mV", "mV", "62", "-62"], "1":["600mV", "mV", "620", "-620"]}, 10 | "OHM":{"0":["600Ω", "Ω", "620", "-620"], "1":["6kΩ", "kΩ", "6.2", "-6.2"], "2":["60kΩ", "kΩ", "62", "-62"], "3":["600kΩ", "kΩ", "620", "-620"], "4":["6MΩ", "MΩ", "6.2", "-6.2"], "5":["60MΩ", "MΩ", "62", "-62"]}, 11 | "CAP":{"0":["60nF", "nF", "62", "-62"], "1":["600nF", "nF", "620", "-620"], "2":["6uF", "uF", "6.2", "-6.2"], "3":["60uF", "uF", "62", "-62"], "4":["600uF", "uF", "620", "-620"], "5":["6mF", "mF", "6.2", "-6.2"], "6":["60mF", "mF", "62", "-62"]}, 12 | "DCuA":{"0":["600uA", "uA", "620", "-620"], "1":["6000uA", "uA", "6200", "-6200"]}, 13 | "DCmA":{"0":["60mA", "mA", "62", "-62"], "1":["600mA", "mA", "620", "-620"]}, 14 | "DCA":{"0":["6A", "A", "6.2", "-6.2"], "1":["20A", "A", "20.1", "-20.1"]}, 15 | "ACuA":{ "0":["600uA", "uA", "620", "-620"], "1":["6000uA", "uA", "6200", "-6200"]}, 16 | "ACmA":{"0":["60mA", "mA", "62", "-62"], "1":["600mA", "mA", "620", "-620"]}, 17 | "ACA":{"0":["6A", "A", "6.2", "-6.2"], "1":["20A", "A", "20.1", "-20.1"]}, 18 | "AC+DC":{"0":["6A", "A", "6.2", "-6.2"], "1":["20A", "A", "20.1", "-20.1"]}, 19 | "AC+DC2":{"0":["6A", "A", "6.2", "-6.2"], "1":["20A", "A", "20.1", "-20.1"]}, 20 | "Hz":{"0":["99.99Hz", "Hz", "100", "-100"], "1":["999.9Hz", "Hz", "1000", "-1000"], "2":["9.999kHz", "kHz", "10", "-10"], "3":["99.99kHz", "kHz", "100", "-100"], "4":["999.9kHz", "kHz", "1000", "-1000"], "5":["9.999mHz", "MHz", "10", "-10"]}, 21 | "°C":{"0":["-40°C~300°C", "°C", "310", "-50"], "1":["300°C~1000°C", "°C", "1020", "-50"]}, 22 | "°F":{"0":["-40°F~572°F", "°F", "590", "-58"], "1":["572°F~1832°F", "°F", "1868", "-58"]}, 23 | "DIDOE": {"0": ["3V", "V", "3.1", "-3.1"]}, 24 | "CONT": {"0": ["600Ω", "Ω", "620", "-620"]} 25 | } 26 | } -------------------------------------------------------------------------------- /from_vendor/funOl_UT61E+.json: -------------------------------------------------------------------------------- 1 | { 2 | "OL": { 3 | "DCV": {"0":["2.2V", "V", "2.3", "-2.3"], "1":["22V", "V", "23", "-23"], "2":["220V", "V", "230", "-230"], "3":["1000V", "V", "1010", "-1010"]}, 4 | "ACV":{"0":["2.2V", "V", "2.3", "-2.3"], "1":["22V", "V", "23", "-23"], "2":["220V", "V", "230", "-230"], "3":["1000V", "V", "1010", "-1010"]}, 5 | "LPF":{"0":["2.2V", "V", "2.3", "-2.3"], "1":["22V", "V", "23", "-23"], "2":["220V", "V", "230", "-230"], "3":["1000V", "V", "1010", "-1010"]}, 6 | "LozV":{"0":["2.2V", "V", "2.3", "-2.3"], "1":["22V", "V", "23", "-23"], "2":["220V", "V", "230", "-230"], "3":["1000V", "V", "1010", "-1010"]}, 7 | "DCmV":{"0":["220mV", "mV", "230", "-230"]}, 8 | "ACmV":{"0":["220mV", "mV", "230", "-230"]}, 9 | "OHM":{"0":["220Ω", "Ω", "230", "-230"], "1":["2.2kΩ", "kΩ", "2.3", "-2.3"], "2":["22kΩ", "kΩ", "23", "-23"], "3":["220kΩ", "kΩ", "230", "-230"], "4":["2.2MΩ", "MΩ", "2.3", "-2.3"], "5":["22MΩ", "MΩ", "23", "-23"], "6":["220MΩ", "MΩ", "230", "-230"]}, 10 | "CAP":{"0":["22nF", "nF", "23", "-23"], "1":["220nF", "nF", "230", "-230"], "2":["2.2uF", "uF", "2.3", "-2.3"], "3":["22uF", "uF", "23", "-23"], "4":["230uF", "uF", "230", "-230"], "5":["2.2mF", "mF", "2.3", "-2.3"], "6":["22mF", "mF", "23", "-23"], "7":["220mF", "mF", "230", "-230"]}, 11 | "DCuA":{"0":["220uA", "uA", "230", "-230"], "1":["2200uA", "uA", "2300", "-2300"]}, 12 | "DCmA":{"0":["22mA", "mA", "23", "-23"], "1":["220mA", "mA", "230", "-230"]}, 13 | "DCA":{"1":["20A", "A", "20.1", "-20.1"]}, 14 | "ACuA":{"0":["220uA", "uA", "230", "-230"], "1":["2200uA", "uA", "2300", "-2300"]}, 15 | "ACmA":{"0":["22mA", "mA", "23", "-23"], "1":["220mA", "mA", "230", "-230"]}, 16 | "ACA":{"1":["20A", "A", "20.1", "-20.1"]}, 17 | "AC+DC":{"1":["20A", "A", "20.1", "-20.1"]}, 18 | "AC+DC2":{"1":["20A", "A", "20.1", "-20.1"]}, 19 | "Hz":{"0":["22Hz", "Hz", "23", "-23"], "1":["220Hz", "Hz", "230", "-230"], "2":["2.2kHz", "kHz", "2.3", "-2.3"], "3":["22kHz", "kHz", "23", "-23"], "4":["220kHz", "kHz", "230", "-230"], "5":["2.2mHz", "MHz", "2.3", "-2.3"], "6":["22mHz", "MHz", "23", "-23"], "7":["220mHz", "MHz", "230", "-230"]}, 20 | "°C":{"0":["-40°C~300°C", "°C", "310", "-50"], "1":["300°C~1000°C", "°C", "1020", "-50"]}, 21 | "°F":{"0":["-40°F~572°F", "°F", "590", "-58"], "1":["572°F~1832°F", "°F", "1868", "-58"]}, 22 | "AC/DC":{"0":["2.2V", "V", "2.3", "-2.3"], "1":["22V", "V", "23", "-23"], "2":["220V", "V", "230", "-230"], "3":["1000V", "V", "1010", "-1010"]}, 23 | "DIDOE": {"0": ["3.0V", "V", "2.3", "-2.3"]}, 24 | "CONT": {"0": ["220Ω", "Ω", "230", "-230"]}, 25 | "%": {"0": ["100%", "%", "110", "-110"]} 26 | } 27 | } -------------------------------------------------------------------------------- /getUnits.py: -------------------------------------------------------------------------------- 1 | # small helper to convert JSON file 2 | import json 3 | import pprint 4 | 5 | units = {} 6 | prefixes = set() 7 | 8 | with open('from_vendor/funOl_UT61E+.json') as src: 9 | data = json.load(src) 10 | print(data) 11 | for mode, d in data['OL'].items(): 12 | units[mode] = {} 13 | for range, r in d.items(): 14 | units[mode][range] = r[1] 15 | prefixes.add( r[1][0] ) 16 | 17 | print() 18 | pprint.pprint(units) 19 | print() 20 | pprint.pprint(prefixes) -------------------------------------------------------------------------------- /mqtt_bridge: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pip3 install --user paho-mqtt 4 | 5 | import paho.mqtt.client as mqtt 6 | import json 7 | import logging 8 | import datetime 9 | import argparse 10 | import time 11 | 12 | import decimal 13 | from ut61eplus import UT61EPLUS 14 | 15 | log = logging.getLogger(__name__) 16 | cmdline : dict = None 17 | dmm : UT61EPLUS = None 18 | 19 | class MyClient(mqtt.Client): 20 | def __init__(self, mid): 21 | self._last_data : datetime = None 22 | super().__init__(mid) 23 | 24 | def loop_misc(self): 25 | log.debug('loop') 26 | res = super().loop_misc() 27 | 28 | now = datetime.datetime.now() 29 | if self._last_data is None: 30 | self._last_data = now 31 | elif (now - self._last_data).total_seconds() >= cmdline.interval: 32 | self._last_data = now 33 | send_data(self) 34 | 35 | print('return') 36 | return res 37 | 38 | def send_data(client : mqtt.Client): 39 | log.debug('send data') 40 | m = dmm.takeMeasurement() 41 | log.debug('measurement=%s', m) 42 | v : str = None 43 | if m.value.is_infinite(): 44 | v = 'overflow' 45 | else: 46 | v = '{0:f}'.format(m.value) 47 | 48 | data = { 49 | 'mode': m.mode, 50 | 'dc': m.isDC, 51 | 'unit': m.unit, 52 | 'value': v, 53 | 'battery_warning': m.hasBatteryWarning, 54 | } 55 | log.debug('sending %s', data) 56 | client.publish(cmdline.mqtt_topic, json.dumps(data)) 57 | 58 | def on_connect(client, userdata, flags, rc): 59 | log.info('connected to mqtt') 60 | 61 | def on_message(client, userdata, msg): 62 | print(msg.topic + ' ' + str(msg.payload)) 63 | 64 | def main(): 65 | global cmdline, dmm 66 | 67 | parser = argparse.ArgumentParser(description='mqtt bridge') 68 | 69 | parser.add_argument('--mqtt-client-id', type=str, required=False, help='client id connecting to mqtt server') 70 | parser.add_argument('--mqtt-host', type=str, required=True, help='mqtt server') 71 | parser.add_argument('--mqtt-port', type=int, required=False, default=1883, help='mqtt server') 72 | parser.add_argument('--mqtt-user', type=str, required=False, help='mqtt username') 73 | parser.add_argument('--mqtt-password', type=str, required=False, help='mqtt password') 74 | 75 | parser.add_argument('--mqtt-topic', type=str, required=True, help='measurement topic') 76 | parser.add_argument('--interval', type=float, required=True, help='interval for measurement in seconds') 77 | 78 | parser.add_argument('--debug', required=False, action='store_true', help='enable debug logging') 79 | 80 | cmdline = parser.parse_args() 81 | log.debug('cmdline=%s', cmdline) 82 | 83 | if cmdline.debug: 84 | logging.basicConfig(level=logging.DEBUG) 85 | else: 86 | logging.basicConfig(level=logging.INFO) 87 | 88 | 89 | log.info('opening DMM') 90 | dmm = UT61EPLUS() 91 | dmm_name = dmm.getName() 92 | log.info('DMM:%s', dmm_name) 93 | mqtt_name = cmdline.mqtt_client_id 94 | if mqtt_name is None: 95 | mqtt_name = dmm_name 96 | 97 | mqtt_client = MyClient(mqtt_name) 98 | mqtt_client.username_pw_set(cmdline.mqtt_user, cmdline.mqtt_password) 99 | mqtt_client.on_connect = on_connect 100 | mqtt_client.connect(cmdline.mqtt_host, cmdline.mqtt_port) 101 | 102 | while True: 103 | try: 104 | timeout = 1 105 | if cmdline.interval < 5: 106 | timeout = 0.3 107 | 108 | mqtt_client.loop_forever(timeout=timeout) 109 | except Exception: 110 | log.exception('error in mqtt loop') 111 | time.sleep(10) 112 | 113 | if __name__ == '__main__': 114 | main() 115 | -------------------------------------------------------------------------------- /plotting-live-data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import matplotlib.pyplot as plt 3 | import matplotlib.animation as animation 4 | from matplotlib import style 5 | import numpy as np 6 | from ut61eplus import UT61EPLUS 7 | 8 | dmm = UT61EPLUS() 9 | utname = dmm.getName() 10 | fig = plt.figure() 11 | ax = fig.add_subplot(1, 1, 1) 12 | xs = [0] 13 | ys = [0] 14 | i = 1 15 | 16 | def animate(i, xs, ys): 17 | mesure=dmm.takeMeasurement() 18 | if not mesure.overload : 19 | xs.append(i) 20 | ys.append(mesure.display_decimal) 21 | ax.clear() 22 | ax.plot(xs, ys, label=mesure.mode) 23 | xs = xs[-50:] 24 | ys = ys[-50:] 25 | plt.xticks(rotation=45, ha='right') 26 | plt.subplots_adjust(bottom=0.30) 27 | plt.title(utname) 28 | plt.ylabel(mesure.display_unit) 29 | plt.legend() 30 | plt.axis([0 if i < 50 else i-50, 50 if i < 50 else i, min(ys), max(ys)]) 31 | i=i+1 32 | 33 | ani = animation.FuncAnimation(fig, animate, fargs=(xs, ys), interval=200) 34 | plt.show() 35 | -------------------------------------------------------------------------------- /readDMM.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | from ut61eplus import UT61EPLUS 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | logging.basicConfig(level=logging.DEBUG) 8 | 9 | dmm = UT61EPLUS() 10 | 11 | log.info('name=%s', dmm.getName()) 12 | dmm.sendCommand('lamp') 13 | m = dmm.takeMeasurement() 14 | log.info('measurent=%s', m) 15 | 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | hidapi 2 | -------------------------------------------------------------------------------- /ut61eplus/__init__.py: -------------------------------------------------------------------------------- 1 | from .ut61eplus import UT61EPLUS -------------------------------------------------------------------------------- /ut61eplus/ut61eplus.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import logging 3 | import hid # https://github.com/trezor/cython-hidapi https://trezor.github.io/cython-hidapi/api.html 4 | import decimal 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | """ 9 | protocol of UT61+ 10 | 11 | parts from an USB trace, parts from experimenting myself, parts from https://github.com/gulux/Uni-T-CP2110 12 | and many 'inspirations' form the decompiled bluetooth app 13 | 14 | example response in mV AC 15 | 16 | ab . => header 17 | cd . => header 18 | 10 => number of bytes that follow including 'checksum' 19 | 01 => mode 20 | 30 0 => range (character starting at '0') 21 | 20 => digit MSB (can be ' ' or '-') ! number can also be ' OL. ' 22 | 20 => digit 23 | 35 5 => digit 24 | 33 3 => digit 25 | 2e . => digit 26 | 35 5 => digit 27 | 34 4 => digit LSB 28 | 01 => progress1 29 | 00 => progress2 => progress = progress1*10 + progress2 - meaning is not clear yet 30 | 30 0 => Bitmask: Max,Min,Hold,Rel 31 | 34 4 => Bitmask: !Auto,Battery,HvWarning 32 | 30 0 => Bitmask: !DC,PeakMax,PeakMin,BarPol 33 | 03 => sum over all - MSB - sum from 0xab to 0x30 34 | 8d . => sum over all - LSB 35 | 36 | """ 37 | 38 | 39 | class Measurement: 40 | 41 | # decoded modes 42 | _MODE = ['ACV', 'ACmV', 'DCV', 'DCmV', 'Hz', '%', 'OHM', 'CONT', 'DIDOE', 'CAP', '°C', '°F', 'DCuA', 'ACuA', 'DCmA', 'ACmA', 43 | 'DCA', 'ACA', 'HFE', 'Live', 'NCV', 'LozV', 'ACA', 'DCA', 'LPF', 'AC/DC', 'LPF', 'AC+DC', 'LPF', 'AC+DC2', 'INRUSH'] 44 | 45 | # units based on mode and range 46 | _UNITS = {'%': {'0': '%'}, 47 | 'AC+DC': {'1': 'A'}, 48 | 'AC+DC2': {'1': 'A'}, 49 | 'AC/DC': {'0': 'V', '1': 'V', '2': 'V', '3': 'V'}, 50 | 'ACA': {'1': 'A'}, 51 | 'ACV': {'0': 'V', '1': 'V', '2': 'V', '3': 'V'}, 52 | 'ACmA': {'0': 'mA', '1': 'mA'}, 53 | 'ACmV': {'0': 'mV'}, 54 | 'ACuA': {'0': 'uA', '1': 'uA'}, 55 | 'CAP': {'0': 'nF', 56 | '1': 'nF', 57 | '2': 'uF', 58 | '3': 'uF', 59 | '4': 'uF', 60 | '5': 'mF', 61 | '6': 'mF', 62 | '7': 'mF'}, 63 | 'CONT': {'0': 'Ω'}, 64 | 'DCA': {'1': 'A'}, 65 | 'DCV': {'0': 'V', '1': 'V', '2': 'V', '3': 'V'}, 66 | 'DCmA': {'0': 'mA', '1': 'mA'}, 67 | 'DCmV': {'0': 'mV'}, 68 | 'DCuA': {'0': 'uA', '1': 'uA'}, 69 | 'DIDOE': {'0': 'V'}, 70 | 'Hz': {'0': 'Hz', 71 | '1': 'Hz', 72 | '2': 'kHz', 73 | '3': 'kHz', 74 | '4': 'kHz', 75 | '5': 'MHz', 76 | '6': 'MHz', 77 | '7': 'MHz'}, 78 | 'LPF': {'0': 'V', '1': 'V', '2': 'V', '3': 'V'}, 79 | 'LozV': {'0': 'V', '1': 'V', '2': 'V', '3': 'V'}, 80 | 'OHM': {'0': 'Ω', 81 | '1': 'kΩ', 82 | '2': 'kΩ', 83 | '3': 'kΩ', 84 | '4': 'MΩ', 85 | '5': 'MΩ', 86 | '6': 'MΩ'}, 87 | '°C': {'0': '°C', '1': '°C'}, 88 | '°F': {'0': '°F', '1': '°F'}, 89 | 'HFE': {'0': 'B'}, 90 | 'NCV': {'0': 'NCV'}} 91 | 92 | # strings that could mean overload - taken from android app 93 | _OVERLOAD = set(['.OL', 'O.L', 'OL.', 'OL', '-.OL', '-O.L', '-OL.', '-OL']) 94 | 95 | # strings that Indicate level of voltage detected >=50Vrms (50-60Hz) 96 | _NCV = set(['EF','-','--','---','----','-----']) 97 | 98 | # unit exponents 99 | _EXPONENTS = { 100 | 'M': 6, # mega 101 | 'k': 3, # kilo 102 | 'm': -3, # milli 103 | 'u': -6, # mirco 104 | 'n': -9, # nano 105 | } 106 | 107 | @property 108 | def binary(self)->bytes: 109 | """ original binary data from DMM """ 110 | return self._data['binary'] 111 | 112 | @property 113 | def mode(self)->str: 114 | """ mode """ 115 | return self._data['mode'] 116 | 117 | @property 118 | def range(self)->str: 119 | """ range - internal to device """ 120 | return self._data['range'] 121 | 122 | @property 123 | def display(self)->str: 124 | """displayed number as string """ 125 | return self._data['display'] 126 | 127 | @property 128 | def overload(self)->bool: 129 | """ device is in overload condition - like measuring resistance on open leads """ 130 | return self._data['overload'] 131 | 132 | @property 133 | def display_decimal(self)->decimal: 134 | """ displayed number as decimal - may be decimal.Overflow() in overload condition """ 135 | return self._data['display_decimal'] 136 | 137 | @property 138 | def display_unit(self)->str: 139 | """ displayed unit including exponent - e.g. mV """ 140 | return self._data['display_unit'] 141 | 142 | @property 143 | def unit(self)->str: 144 | """ physical unit of the measurement - e.g. V """ 145 | return self._data['display_unit'] 146 | 147 | @property 148 | def value(self)->decimal: 149 | """ decimal representation - e.g. 200mV => 0.2V """ 150 | return self._data['decimal'] 151 | 152 | @property 153 | def progress(self)->int: 154 | """ some progress indicator - unknown meaning """ 155 | return self._data['progres'] 156 | 157 | @property 158 | def isMax(self)->bool: 159 | """ value is max value """ 160 | return self._data['max'] 161 | 162 | @property 163 | def isMin(self)->bool: 164 | """ value is max value """ 165 | return self._data['min'] 166 | 167 | @property 168 | def isHold(self)->bool: 169 | """ DMM is in hold mode """ 170 | return self._data['hold'] 171 | 172 | @property 173 | def isRel(self)->bool: 174 | """ DMM is in REL mode """ 175 | return self._data['rel'] 176 | 177 | @property 178 | def isAuto(self)->bool: 179 | """ auto ranging active """ 180 | return self._data['auto'] 181 | 182 | @property 183 | def hasBatteryWarning(self)->bool: 184 | """ battery warning """ 185 | return self._data['battery'] 186 | 187 | @property 188 | def hasHVWarning(self)->bool: 189 | """ high voltage warning - > 30 V """ 190 | return self._data['hvwarning'] 191 | 192 | @property 193 | def isDC(self)->bool: 194 | """ displayed value is DC """ 195 | return self._data['dc'] 196 | 197 | @property 198 | def isMaxPeak(self)->bool: 199 | """ value is max peak """ 200 | return self._data['peak_max'] 201 | 202 | @property 203 | def isMinPeak(self)->bool: 204 | """ value is min peak """ 205 | return self._data['peak_min'] 206 | 207 | @property 208 | def isBarPol(self)->bool: 209 | """ unknown """ 210 | return self._data[''] 211 | 212 | 213 | def __init__(self, b: bytes): 214 | self._data = {} 215 | self._data['binary'] = b 216 | self._data['mode'] = self._MODE[b[0]] 217 | self._data['range'] = b[1:2].decode('ASCII') 218 | self._data['display'] = b[2:9].decode('ASCII').replace(' ', '') 219 | self._data['overload'] = self._data['display'] in self._OVERLOAD 220 | self._data['ncv'] = self._data['display'] in self._NCV 221 | if self._data['overload']: 222 | self._data['display_decimal'] = decimal.Overflow() 223 | elif self._data['ncv']: 224 | switch={ 225 | 'EF': 0, 226 | '-': 1, 227 | '--': 2, 228 | '---': 3, 229 | '----': 4, 230 | '-----': 5 231 | } 232 | self._data['display_decimal'] = switch.get(self._data['display'],-1) 233 | else: 234 | self._data['display_decimal'] = decimal.Decimal(self.display) 235 | 236 | self._data['display_unit'] = self._UNITS[ self._data['mode'] ].get(self._data['range']) 237 | 238 | self._data['unit'] = self._data['display_unit'] 239 | 240 | self._data['decimal'] = self.display_decimal 241 | if self._data['unit'][0] in self._EXPONENTS and not self._data['overload']: 242 | self._data['decimal'] = self._data['decimal'].rotate(self._EXPONENTS[self.unit[0]]) 243 | self._data['unit'] = self._data['unit'][1:] # remove first char 244 | 245 | self._data['progres'] = b[9] * 10 + b[10] 246 | self._data['max'] = b[11] & 8 > 0 247 | self._data['min'] = b[11] & 4 > 0 248 | self._data['hold'] = b[11] & 2 > 0 249 | self._data['rel'] = b[11] & 1 > 0 250 | self._data['auto'] = b[12] & 4 == 0 251 | self._data['battery'] = b[12] & 2 > 0 252 | self._data['hvwarning'] = b[12] & 1 > 0 253 | self._data['dc'] = b[13] & 8 > 0 254 | self._data['peak_max'] = b[13] & 4 > 0 255 | self._data['peak_min'] = b[13] & 2 > 0 256 | self._data['bar_pol'] = b[13] & 1 > 0 # meaning not clear 257 | 258 | def __str__(self): 259 | res = '\n' 260 | res += f'mode={self.mode}\n' 261 | res += f'range={self.range}\n' 262 | res += f'display={self.display}\n' 263 | res += f'display_decimal={self.display_decimal}\n' 264 | res += f'display_unit={self.display_unit}\n' 265 | res += f'overload={self.overload}\n' 266 | res += f'value={self.value}\n' 267 | res += f'unit={self.unit}\n' 268 | res += f'isMax={self.isMax}\n' 269 | res += f'ismin={self.isMin}\n' 270 | res += f'isHold={self.isHold}\n' 271 | res += f'isRel={self.isRel}\n' 272 | res += f'isAuto={self.isAuto}\n' 273 | res += f'hasBatteryWarning={self.hasBatteryWarning}\n' 274 | res += f'hashasHVWarning={self.hasHVWarning}\n' 275 | res += f'isDC={self.isDC}\n' 276 | res += f'isMaxPeak={self.isMaxPeak}\n' 277 | res += f'isMinPeak={self.isMinPeak}\n' 278 | return res 279 | 280 | # pylint: disable=unreachable 281 | for b in self.binary: 282 | l = '{:02x} {}\n'.format(b, chr(b)) 283 | res += l 284 | return res 285 | 286 | 287 | class UT61EPLUS: 288 | 289 | CP2110_VID = 0x10c4 290 | CP2110_PID = 0xEA80 291 | 292 | _SEQUENCE_GET_NAME = bytes.fromhex('AB CD 03 5F 01 DA') 293 | _SEQUENCE_SEND_DATA = bytes.fromhex('AB CD 03 5E 01 D9') 294 | _SEQUENCE_SEND_CMD = bytes.fromhex('AB CD 03') 295 | 296 | _COMMANDS = { 297 | 'min_max': 65, 298 | 'not_min_max': 66, 299 | 'range': 70, 300 | 'auto': 71, 301 | 'rel': 72, 302 | 'select2': 73, # Hz/USB 303 | 'hold': 74, 304 | 'lamp': 75, 305 | 'select1': 76, # orange 306 | 'p_min_max': 77, 307 | 'not_peak': 78, 308 | } 309 | 310 | def __init__(self): 311 | """open device""" 312 | self.dev = hid.device() 313 | self.dev.open(self.CP2110_VID, self.CP2110_PID) 314 | log.debug('device is open') 315 | #self.dev.nonblocking = 1 316 | self.dev.send_feature_report([0x41, 0x01]) # enable uart 317 | self.dev.send_feature_report([0x50, 0x00, 0x00, 0x25, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00]) # 9600 8N1 - from USB trace 318 | self.dev.send_feature_report([0x43, 0x02]) # purge both fifos 319 | log.debug('feature requests sent') 320 | 321 | def _write(self, b: bytes): 322 | buf = [] 323 | buf.append(len(b)) 324 | buf += b 325 | self.dev.write(buf) 326 | 327 | def _readResponse(self) -> bytes: 328 | # pylint: disable=unsupported-assignment-operation,unsubscriptable-object 329 | state = 0 # 0=init 1=0xAB received 2=0xCD received 3=we have length 330 | buf: bytes = None 331 | index: int = None 332 | sum: int = 0 333 | 334 | while True: 335 | x = self.dev.read(64) 336 | b: int 337 | for b in x[1:]: # skip first byte - length from HID 338 | 339 | if state < 3 or index + 2 < len(buf): # sum all bytes except last 2 340 | sum += b 341 | 342 | if state == 0 and b == 0xAB: 343 | state = 1 344 | elif state == 1 and b == 0xCD: 345 | state = 2 346 | elif state == 2: 347 | buf = bytearray(b) 348 | index = 0 349 | state = 3 350 | elif state == 3: 351 | buf[index] = b 352 | index += 1 353 | if index == len(buf): 354 | recevied_sum = (buf[-2] << 8) + buf[-1] 355 | log.debug('calculated sum=%04x expected sum=%04x', sum, recevied_sum) 356 | if sum != recevied_sum: 357 | log.warning('checksum mismatch') 358 | return None 359 | return buf[:-2] # drop last 2 bytes at end with checksum 360 | else: 361 | log.warning('unexpected byte %02x in state %i', b, state) 362 | 363 | def getName(self): 364 | # pylint: disable=unused-variable 365 | """get name of multimeter""" 366 | self._write(self._SEQUENCE_GET_NAME) 367 | unknown = self._readResponse() 368 | name = self._readResponse() 369 | return name.decode('ASCII') 370 | 371 | def takeMeasurement(self): 372 | """read measurement from screen""" 373 | self._write(self._SEQUENCE_SEND_DATA) 374 | b = self._readResponse() 375 | if b is None: 376 | return None 377 | return Measurement(b) 378 | 379 | def sendCommand(self, cmd)->None: 380 | """send command to device""" 381 | if cmd in self._COMMANDS: 382 | cmd = self._COMMANDS[cmd] 383 | if not type(cmd) is int: 384 | raise Exception(f'bad argument {cmd}') 385 | 386 | seq = self._SEQUENCE_SEND_CMD 387 | cmd_bytes = bytearray(3) 388 | cmd_bytes[0] = cmd & 0xff 389 | cmd = cmd + 379 # don't ask it's from the java source 390 | cmd_bytes[1] = cmd >> 8 391 | cmd_bytes[2] = cmd & 0xff 392 | seq += cmd_bytes 393 | self._write(seq) 394 | # pylint: disable=unused-variable 395 | unknown = self._readResponse() 396 | 397 | def _test(self): 398 | self._write(self._SEQUENCE_GET_NAME) 399 | 400 | while True: 401 | x = self.dev.read(64) 402 | for i in range(1, len(x)): # skip first byte - length 403 | hex: str = binascii.hexlify(x[i:i+1]).decode('ASCII') 404 | c: str = x[i:i+1].decode(encoding='ASCII', errors='ignore') 405 | if c.isprintable and len(c) == 1: 406 | pass 407 | else: 408 | c = '.' 409 | print(f'{hex} {c}') 410 | 411 | --------------------------------------------------------------------------------