├── .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 |
--------------------------------------------------------------------------------