├── .gitignore
├── COPYING.txt
├── MANIFEST.in
├── README.txt
├── RFXtrx
├── __init__.py
├── dummy.py
├── lowlevel.py
├── pyserial.py
└── twistedserial.py
├── doctest
├── all_versions.sh
├── lighting.txt
└── lowlevel.txt
├── examples
├── receive.py
├── receive_twisted.py
├── send.py
└── send_twisted.py
├── lint_run.sh
├── setup.py
└── test_run.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 | __pycache__
3 |
4 | # C extensions
5 | *.so
6 |
7 | # Packages
8 | *.egg
9 | *.egg-info
10 | dist
11 | build
12 | eggs
13 | parts
14 | bin
15 | var
16 | sdist
17 | develop-eggs
18 | .installed.cfg
19 | lib
20 | lib64
21 |
22 | # Generated documentation
23 | docs
24 |
25 | # Installer logs
26 | pip-log.txt
27 |
28 | # Unit test / coverage reports
29 | .coverage
30 | .tox
31 | nosetests.xml
32 |
33 | # Translations
34 | *.mo
35 |
36 | # Mr Developer
37 | .mr.developer.cfg
38 | .project
39 | .pydevproject
40 | .settings
41 |
--------------------------------------------------------------------------------
/COPYING.txt:
--------------------------------------------------------------------------------
1 | This file is part of pyRFXtrx, a Python library to communicate with
2 | the RFXtrx family of devices from http://www.rfxcom.com/
3 | See https://github.com/woudt/pyRFXtrx for the latest version.
4 |
5 | This file contains the GNU LGPL license, under which pyRFXtrx is released.
6 | As the GNU LGPL references the GNU GPL, the latter is included after the first
7 | in this document.
8 |
9 | ===============================================================================
10 |
11 | GNU LESSER GENERAL PUBLIC LICENSE
12 | Version 3, 29 June 2007
13 |
14 | Copyright (C) 2007 Free Software Foundation, Inc.
15 | Everyone is permitted to copy and distribute verbatim copies
16 | of this license document, but changing it is not allowed.
17 |
18 |
19 | This version of the GNU Lesser General Public License incorporates
20 | the terms and conditions of version 3 of the GNU General Public
21 | License, supplemented by the additional permissions listed below.
22 |
23 | 0. Additional Definitions.
24 |
25 | As used herein, "this License" refers to version 3 of the GNU Lesser
26 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
27 | General Public License.
28 |
29 | "The Library" refers to a covered work governed by this License,
30 | other than an Application or a Combined Work as defined below.
31 |
32 | An "Application" is any work that makes use of an interface provided
33 | by the Library, but which is not otherwise based on the Library.
34 | Defining a subclass of a class defined by the Library is deemed a mode
35 | of using an interface provided by the Library.
36 |
37 | A "Combined Work" is a work produced by combining or linking an
38 | Application with the Library. The particular version of the Library
39 | with which the Combined Work was made is also called the "Linked
40 | Version".
41 |
42 | The "Minimal Corresponding Source" for a Combined Work means the
43 | Corresponding Source for the Combined Work, excluding any source code
44 | for portions of the Combined Work that, considered in isolation, are
45 | based on the Application, and not on the Linked Version.
46 |
47 | The "Corresponding Application Code" for a Combined Work means the
48 | object code and/or source code for the Application, including any data
49 | and utility programs needed for reproducing the Combined Work from the
50 | Application, but excluding the System Libraries of the Combined Work.
51 |
52 | 1. Exception to Section 3 of the GNU GPL.
53 |
54 | You may convey a covered work under sections 3 and 4 of this License
55 | without being bound by section 3 of the GNU GPL.
56 |
57 | 2. Conveying Modified Versions.
58 |
59 | If you modify a copy of the Library, and, in your modifications, a
60 | facility refers to a function or data to be supplied by an Application
61 | that uses the facility (other than as an argument passed when the
62 | facility is invoked), then you may convey a copy of the modified
63 | version:
64 |
65 | a) under this License, provided that you make a good faith effort to
66 | ensure that, in the event an Application does not supply the
67 | function or data, the facility still operates, and performs
68 | whatever part of its purpose remains meaningful, or
69 |
70 | b) under the GNU GPL, with none of the additional permissions of
71 | this License applicable to that copy.
72 |
73 | 3. Object Code Incorporating Material from Library Header Files.
74 |
75 | The object code form of an Application may incorporate material from
76 | a header file that is part of the Library. You may convey such object
77 | code under terms of your choice, provided that, if the incorporated
78 | material is not limited to numerical parameters, data structure
79 | layouts and accessors, or small macros, inline functions and templates
80 | (ten or fewer lines in length), you do both of the following:
81 |
82 | a) Give prominent notice with each copy of the object code that the
83 | Library is used in it and that the Library and its use are
84 | covered by this License.
85 |
86 | b) Accompany the object code with a copy of the GNU GPL and this license
87 | document.
88 |
89 | 4. Combined Works.
90 |
91 | You may convey a Combined Work under terms of your choice that,
92 | taken together, effectively do not restrict modification of the
93 | portions of the Library contained in the Combined Work and reverse
94 | engineering for debugging such modifications, if you also do each of
95 | the following:
96 |
97 | a) Give prominent notice with each copy of the Combined Work that
98 | the Library is used in it and that the Library and its use are
99 | covered by this License.
100 |
101 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
102 | document.
103 |
104 | c) For a Combined Work that displays copyright notices during
105 | execution, include the copyright notice for the Library among
106 | these notices, as well as a reference directing the user to the
107 | copies of the GNU GPL and this license document.
108 |
109 | d) Do one of the following:
110 |
111 | 0) Convey the Minimal Corresponding Source under the terms of this
112 | License, and the Corresponding Application Code in a form
113 | suitable for, and under terms that permit, the user to
114 | recombine or relink the Application with a modified version of
115 | the Linked Version to produce a modified Combined Work, in the
116 | manner specified by section 6 of the GNU GPL for conveying
117 | Corresponding Source.
118 |
119 | 1) Use a suitable shared library mechanism for linking with the
120 | Library. A suitable mechanism is one that (a) uses at run time
121 | a copy of the Library already present on the user's computer
122 | system, and (b) will operate properly with a modified version
123 | of the Library that is interface-compatible with the Linked
124 | Version.
125 |
126 | e) Provide Installation Information, but only if you would otherwise
127 | be required to provide such information under section 6 of the
128 | GNU GPL, and only to the extent that such information is
129 | necessary to install and execute a modified version of the
130 | Combined Work produced by recombining or relinking the
131 | Application with a modified version of the Linked Version. (If
132 | you use option 4d0, the Installation Information must accompany
133 | the Minimal Corresponding Source and Corresponding Application
134 | Code. If you use option 4d1, you must provide the Installation
135 | Information in the manner specified by section 6 of the GNU GPL
136 | for conveying Corresponding Source.)
137 |
138 | 5. Combined Libraries.
139 |
140 | You may place library facilities that are a work based on the
141 | Library side by side in a single library together with other library
142 | facilities that are not Applications and are not covered by this
143 | License, and convey such a combined library under terms of your
144 | choice, if you do both of the following:
145 |
146 | a) Accompany the combined library with a copy of the same work based
147 | on the Library, uncombined with any other library facilities,
148 | conveyed under the terms of this License.
149 |
150 | b) Give prominent notice with the combined library that part of it
151 | is a work based on the Library, and explaining where to find the
152 | accompanying uncombined form of the same work.
153 |
154 | 6. Revised Versions of the GNU Lesser General Public License.
155 |
156 | The Free Software Foundation may publish revised and/or new versions
157 | of the GNU Lesser General Public License from time to time. Such new
158 | versions will be similar in spirit to the present version, but may
159 | differ in detail to address new problems or concerns.
160 |
161 | Each version is given a distinguishing version number. If the
162 | Library as you received it specifies that a certain numbered version
163 | of the GNU Lesser General Public License "or any later version"
164 | applies to it, you have the option of following the terms and
165 | conditions either of that published version or of any later version
166 | published by the Free Software Foundation. If the Library as you
167 | received it does not specify a version number of the GNU Lesser
168 | General Public License, you may choose any version of the GNU Lesser
169 | General Public License ever published by the Free Software Foundation.
170 |
171 | If the Library as you received it specifies that a proxy can decide
172 | whether future versions of the GNU Lesser General Public License shall
173 | apply, that proxy's public statement of acceptance of any version is
174 | permanent authorization for you to choose that version for the
175 | Library.
176 |
177 | ===============================================================================
178 |
179 | GNU GENERAL PUBLIC LICENSE
180 | Version 3, 29 June 2007
181 |
182 | Copyright (C) 2007 Free Software Foundation, Inc.
183 | Everyone is permitted to copy and distribute verbatim copies
184 | of this license document, but changing it is not allowed.
185 |
186 | Preamble
187 |
188 | The GNU General Public License is a free, copyleft license for
189 | software and other kinds of works.
190 |
191 | The licenses for most software and other practical works are designed
192 | to take away your freedom to share and change the works. By contrast,
193 | the GNU General Public License is intended to guarantee your freedom to
194 | share and change all versions of a program--to make sure it remains free
195 | software for all its users. We, the Free Software Foundation, use the
196 | GNU General Public License for most of our software; it applies also to
197 | any other work released this way by its authors. You can apply it to
198 | your programs, too.
199 |
200 | When we speak of free software, we are referring to freedom, not
201 | price. Our General Public Licenses are designed to make sure that you
202 | have the freedom to distribute copies of free software (and charge for
203 | them if you wish), that you receive source code or can get it if you
204 | want it, that you can change the software or use pieces of it in new
205 | free programs, and that you know you can do these things.
206 |
207 | To protect your rights, we need to prevent others from denying you
208 | these rights or asking you to surrender the rights. Therefore, you have
209 | certain responsibilities if you distribute copies of the software, or if
210 | you modify it: responsibilities to respect the freedom of others.
211 |
212 | For example, if you distribute copies of such a program, whether
213 | gratis or for a fee, you must pass on to the recipients the same
214 | freedoms that you received. You must make sure that they, too, receive
215 | or can get the source code. And you must show them these terms so they
216 | know their rights.
217 |
218 | Developers that use the GNU GPL protect your rights with two steps:
219 | (1) assert copyright on the software, and (2) offer you this License
220 | giving you legal permission to copy, distribute and/or modify it.
221 |
222 | For the developers' and authors' protection, the GPL clearly explains
223 | that there is no warranty for this free software. For both users' and
224 | authors' sake, the GPL requires that modified versions be marked as
225 | changed, so that their problems will not be attributed erroneously to
226 | authors of previous versions.
227 |
228 | Some devices are designed to deny users access to install or run
229 | modified versions of the software inside them, although the manufacturer
230 | can do so. This is fundamentally incompatible with the aim of
231 | protecting users' freedom to change the software. The systematic
232 | pattern of such abuse occurs in the area of products for individuals to
233 | use, which is precisely where it is most unacceptable. Therefore, we
234 | have designed this version of the GPL to prohibit the practice for those
235 | products. If such problems arise substantially in other domains, we
236 | stand ready to extend this provision to those domains in future versions
237 | of the GPL, as needed to protect the freedom of users.
238 |
239 | Finally, every program is threatened constantly by software patents.
240 | States should not allow patents to restrict development and use of
241 | software on general-purpose computers, but in those that do, we wish to
242 | avoid the special danger that patents applied to a free program could
243 | make it effectively proprietary. To prevent this, the GPL assures that
244 | patents cannot be used to render the program non-free.
245 |
246 | The precise terms and conditions for copying, distribution and
247 | modification follow.
248 |
249 | TERMS AND CONDITIONS
250 |
251 | 0. Definitions.
252 |
253 | "This License" refers to version 3 of the GNU General Public License.
254 |
255 | "Copyright" also means copyright-like laws that apply to other kinds of
256 | works, such as semiconductor masks.
257 |
258 | "The Program" refers to any copyrightable work licensed under this
259 | License. Each licensee is addressed as "you". "Licensees" and
260 | "recipients" may be individuals or organizations.
261 |
262 | To "modify" a work means to copy from or adapt all or part of the work
263 | in a fashion requiring copyright permission, other than the making of an
264 | exact copy. The resulting work is called a "modified version" of the
265 | earlier work or a work "based on" the earlier work.
266 |
267 | A "covered work" means either the unmodified Program or a work based
268 | on the Program.
269 |
270 | To "propagate" a work means to do anything with it that, without
271 | permission, would make you directly or secondarily liable for
272 | infringement under applicable copyright law, except executing it on a
273 | computer or modifying a private copy. Propagation includes copying,
274 | distribution (with or without modification), making available to the
275 | public, and in some countries other activities as well.
276 |
277 | To "convey" a work means any kind of propagation that enables other
278 | parties to make or receive copies. Mere interaction with a user through
279 | a computer network, with no transfer of a copy, is not conveying.
280 |
281 | An interactive user interface displays "Appropriate Legal Notices"
282 | to the extent that it includes a convenient and prominently visible
283 | feature that (1) displays an appropriate copyright notice, and (2)
284 | tells the user that there is no warranty for the work (except to the
285 | extent that warranties are provided), that licensees may convey the
286 | work under this License, and how to view a copy of this License. If
287 | the interface presents a list of user commands or options, such as a
288 | menu, a prominent item in the list meets this criterion.
289 |
290 | 1. Source Code.
291 |
292 | The "source code" for a work means the preferred form of the work
293 | for making modifications to it. "Object code" means any non-source
294 | form of a work.
295 |
296 | A "Standard Interface" means an interface that either is an official
297 | standard defined by a recognized standards body, or, in the case of
298 | interfaces specified for a particular programming language, one that
299 | is widely used among developers working in that language.
300 |
301 | The "System Libraries" of an executable work include anything, other
302 | than the work as a whole, that (a) is included in the normal form of
303 | packaging a Major Component, but which is not part of that Major
304 | Component, and (b) serves only to enable use of the work with that
305 | Major Component, or to implement a Standard Interface for which an
306 | implementation is available to the public in source code form. A
307 | "Major Component", in this context, means a major essential component
308 | (kernel, window system, and so on) of the specific operating system
309 | (if any) on which the executable work runs, or a compiler used to
310 | produce the work, or an object code interpreter used to run it.
311 |
312 | The "Corresponding Source" for a work in object code form means all
313 | the source code needed to generate, install, and (for an executable
314 | work) run the object code and to modify the work, including scripts to
315 | control those activities. However, it does not include the work's
316 | System Libraries, or general-purpose tools or generally available free
317 | programs which are used unmodified in performing those activities but
318 | which are not part of the work. For example, Corresponding Source
319 | includes interface definition files associated with source files for
320 | the work, and the source code for shared libraries and dynamically
321 | linked subprograms that the work is specifically designed to require,
322 | such as by intimate data communication or control flow between those
323 | subprograms and other parts of the work.
324 |
325 | The Corresponding Source need not include anything that users
326 | can regenerate automatically from other parts of the Corresponding
327 | Source.
328 |
329 | The Corresponding Source for a work in source code form is that
330 | same work.
331 |
332 | 2. Basic Permissions.
333 |
334 | All rights granted under this License are granted for the term of
335 | copyright on the Program, and are irrevocable provided the stated
336 | conditions are met. This License explicitly affirms your unlimited
337 | permission to run the unmodified Program. The output from running a
338 | covered work is covered by this License only if the output, given its
339 | content, constitutes a covered work. This License acknowledges your
340 | rights of fair use or other equivalent, as provided by copyright law.
341 |
342 | You may make, run and propagate covered works that you do not
343 | convey, without conditions so long as your license otherwise remains
344 | in force. You may convey covered works to others for the sole purpose
345 | of having them make modifications exclusively for you, or provide you
346 | with facilities for running those works, provided that you comply with
347 | the terms of this License in conveying all material for which you do
348 | not control copyright. Those thus making or running the covered works
349 | for you must do so exclusively on your behalf, under your direction
350 | and control, on terms that prohibit them from making any copies of
351 | your copyrighted material outside their relationship with you.
352 |
353 | Conveying under any other circumstances is permitted solely under
354 | the conditions stated below. Sublicensing is not allowed; section 10
355 | makes it unnecessary.
356 |
357 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
358 |
359 | No covered work shall be deemed part of an effective technological
360 | measure under any applicable law fulfilling obligations under article
361 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
362 | similar laws prohibiting or restricting circumvention of such
363 | measures.
364 |
365 | When you convey a covered work, you waive any legal power to forbid
366 | circumvention of technological measures to the extent such circumvention
367 | is effected by exercising rights under this License with respect to
368 | the covered work, and you disclaim any intention to limit operation or
369 | modification of the work as a means of enforcing, against the work's
370 | users, your or third parties' legal rights to forbid circumvention of
371 | technological measures.
372 |
373 | 4. Conveying Verbatim Copies.
374 |
375 | You may convey verbatim copies of the Program's source code as you
376 | receive it, in any medium, provided that you conspicuously and
377 | appropriately publish on each copy an appropriate copyright notice;
378 | keep intact all notices stating that this License and any
379 | non-permissive terms added in accord with section 7 apply to the code;
380 | keep intact all notices of the absence of any warranty; and give all
381 | recipients a copy of this License along with the Program.
382 |
383 | You may charge any price or no price for each copy that you convey,
384 | and you may offer support or warranty protection for a fee.
385 |
386 | 5. Conveying Modified Source Versions.
387 |
388 | You may convey a work based on the Program, or the modifications to
389 | produce it from the Program, in the form of source code under the
390 | terms of section 4, provided that you also meet all of these conditions:
391 |
392 | a) The work must carry prominent notices stating that you modified
393 | it, and giving a relevant date.
394 |
395 | b) The work must carry prominent notices stating that it is
396 | released under this License and any conditions added under section
397 | 7. This requirement modifies the requirement in section 4 to
398 | "keep intact all notices".
399 |
400 | c) You must license the entire work, as a whole, under this
401 | License to anyone who comes into possession of a copy. This
402 | License will therefore apply, along with any applicable section 7
403 | additional terms, to the whole of the work, and all its parts,
404 | regardless of how they are packaged. This License gives no
405 | permission to license the work in any other way, but it does not
406 | invalidate such permission if you have separately received it.
407 |
408 | d) If the work has interactive user interfaces, each must display
409 | Appropriate Legal Notices; however, if the Program has interactive
410 | interfaces that do not display Appropriate Legal Notices, your
411 | work need not make them do so.
412 |
413 | A compilation of a covered work with other separate and independent
414 | works, which are not by their nature extensions of the covered work,
415 | and which are not combined with it such as to form a larger program,
416 | in or on a volume of a storage or distribution medium, is called an
417 | "aggregate" if the compilation and its resulting copyright are not
418 | used to limit the access or legal rights of the compilation's users
419 | beyond what the individual works permit. Inclusion of a covered work
420 | in an aggregate does not cause this License to apply to the other
421 | parts of the aggregate.
422 |
423 | 6. Conveying Non-Source Forms.
424 |
425 | You may convey a covered work in object code form under the terms
426 | of sections 4 and 5, provided that you also convey the
427 | machine-readable Corresponding Source under the terms of this License,
428 | in one of these ways:
429 |
430 | a) Convey the object code in, or embodied in, a physical product
431 | (including a physical distribution medium), accompanied by the
432 | Corresponding Source fixed on a durable physical medium
433 | customarily used for software interchange.
434 |
435 | b) Convey the object code in, or embodied in, a physical product
436 | (including a physical distribution medium), accompanied by a
437 | written offer, valid for at least three years and valid for as
438 | long as you offer spare parts or customer support for that product
439 | model, to give anyone who possesses the object code either (1) a
440 | copy of the Corresponding Source for all the software in the
441 | product that is covered by this License, on a durable physical
442 | medium customarily used for software interchange, for a price no
443 | more than your reasonable cost of physically performing this
444 | conveying of source, or (2) access to copy the
445 | Corresponding Source from a network server at no charge.
446 |
447 | c) Convey individual copies of the object code with a copy of the
448 | written offer to provide the Corresponding Source. This
449 | alternative is allowed only occasionally and noncommercially, and
450 | only if you received the object code with such an offer, in accord
451 | with subsection 6b.
452 |
453 | d) Convey the object code by offering access from a designated
454 | place (gratis or for a charge), and offer equivalent access to the
455 | Corresponding Source in the same way through the same place at no
456 | further charge. You need not require recipients to copy the
457 | Corresponding Source along with the object code. If the place to
458 | copy the object code is a network server, the Corresponding Source
459 | may be on a different server (operated by you or a third party)
460 | that supports equivalent copying facilities, provided you maintain
461 | clear directions next to the object code saying where to find the
462 | Corresponding Source. Regardless of what server hosts the
463 | Corresponding Source, you remain obligated to ensure that it is
464 | available for as long as needed to satisfy these requirements.
465 |
466 | e) Convey the object code using peer-to-peer transmission, provided
467 | you inform other peers where the object code and Corresponding
468 | Source of the work are being offered to the general public at no
469 | charge under subsection 6d.
470 |
471 | A separable portion of the object code, whose source code is excluded
472 | from the Corresponding Source as a System Library, need not be
473 | included in conveying the object code work.
474 |
475 | A "User Product" is either (1) a "consumer product", which means any
476 | tangible personal property which is normally used for personal, family,
477 | or household purposes, or (2) anything designed or sold for incorporation
478 | into a dwelling. In determining whether a product is a consumer product,
479 | doubtful cases shall be resolved in favor of coverage. For a particular
480 | product received by a particular user, "normally used" refers to a
481 | typical or common use of that class of product, regardless of the status
482 | of the particular user or of the way in which the particular user
483 | actually uses, or expects or is expected to use, the product. A product
484 | is a consumer product regardless of whether the product has substantial
485 | commercial, industrial or non-consumer uses, unless such uses represent
486 | the only significant mode of use of the product.
487 |
488 | "Installation Information" for a User Product means any methods,
489 | procedures, authorization keys, or other information required to install
490 | and execute modified versions of a covered work in that User Product from
491 | a modified version of its Corresponding Source. The information must
492 | suffice to ensure that the continued functioning of the modified object
493 | code is in no case prevented or interfered with solely because
494 | modification has been made.
495 |
496 | If you convey an object code work under this section in, or with, or
497 | specifically for use in, a User Product, and the conveying occurs as
498 | part of a transaction in which the right of possession and use of the
499 | User Product is transferred to the recipient in perpetuity or for a
500 | fixed term (regardless of how the transaction is characterized), the
501 | Corresponding Source conveyed under this section must be accompanied
502 | by the Installation Information. But this requirement does not apply
503 | if neither you nor any third party retains the ability to install
504 | modified object code on the User Product (for example, the work has
505 | been installed in ROM).
506 |
507 | The requirement to provide Installation Information does not include a
508 | requirement to continue to provide support service, warranty, or updates
509 | for a work that has been modified or installed by the recipient, or for
510 | the User Product in which it has been modified or installed. Access to a
511 | network may be denied when the modification itself materially and
512 | adversely affects the operation of the network or violates the rules and
513 | protocols for communication across the network.
514 |
515 | Corresponding Source conveyed, and Installation Information provided,
516 | in accord with this section must be in a format that is publicly
517 | documented (and with an implementation available to the public in
518 | source code form), and must require no special password or key for
519 | unpacking, reading or copying.
520 |
521 | 7. Additional Terms.
522 |
523 | "Additional permissions" are terms that supplement the terms of this
524 | License by making exceptions from one or more of its conditions.
525 | Additional permissions that are applicable to the entire Program shall
526 | be treated as though they were included in this License, to the extent
527 | that they are valid under applicable law. If additional permissions
528 | apply only to part of the Program, that part may be used separately
529 | under those permissions, but the entire Program remains governed by
530 | this License without regard to the additional permissions.
531 |
532 | When you convey a copy of a covered work, you may at your option
533 | remove any additional permissions from that copy, or from any part of
534 | it. (Additional permissions may be written to require their own
535 | removal in certain cases when you modify the work.) You may place
536 | additional permissions on material, added by you to a covered work,
537 | for which you have or can give appropriate copyright permission.
538 |
539 | Notwithstanding any other provision of this License, for material you
540 | add to a covered work, you may (if authorized by the copyright holders of
541 | that material) supplement the terms of this License with terms:
542 |
543 | a) Disclaiming warranty or limiting liability differently from the
544 | terms of sections 15 and 16 of this License; or
545 |
546 | b) Requiring preservation of specified reasonable legal notices or
547 | author attributions in that material or in the Appropriate Legal
548 | Notices displayed by works containing it; or
549 |
550 | c) Prohibiting misrepresentation of the origin of that material, or
551 | requiring that modified versions of such material be marked in
552 | reasonable ways as different from the original version; or
553 |
554 | d) Limiting the use for publicity purposes of names of licensors or
555 | authors of the material; or
556 |
557 | e) Declining to grant rights under trademark law for use of some
558 | trade names, trademarks, or service marks; or
559 |
560 | f) Requiring indemnification of licensors and authors of that
561 | material by anyone who conveys the material (or modified versions of
562 | it) with contractual assumptions of liability to the recipient, for
563 | any liability that these contractual assumptions directly impose on
564 | those licensors and authors.
565 |
566 | All other non-permissive additional terms are considered "further
567 | restrictions" within the meaning of section 10. If the Program as you
568 | received it, or any part of it, contains a notice stating that it is
569 | governed by this License along with a term that is a further
570 | restriction, you may remove that term. If a license document contains
571 | a further restriction but permits relicensing or conveying under this
572 | License, you may add to a covered work material governed by the terms
573 | of that license document, provided that the further restriction does
574 | not survive such relicensing or conveying.
575 |
576 | If you add terms to a covered work in accord with this section, you
577 | must place, in the relevant source files, a statement of the
578 | additional terms that apply to those files, or a notice indicating
579 | where to find the applicable terms.
580 |
581 | Additional terms, permissive or non-permissive, may be stated in the
582 | form of a separately written license, or stated as exceptions;
583 | the above requirements apply either way.
584 |
585 | 8. Termination.
586 |
587 | You may not propagate or modify a covered work except as expressly
588 | provided under this License. Any attempt otherwise to propagate or
589 | modify it is void, and will automatically terminate your rights under
590 | this License (including any patent licenses granted under the third
591 | paragraph of section 11).
592 |
593 | However, if you cease all violation of this License, then your
594 | license from a particular copyright holder is reinstated (a)
595 | provisionally, unless and until the copyright holder explicitly and
596 | finally terminates your license, and (b) permanently, if the copyright
597 | holder fails to notify you of the violation by some reasonable means
598 | prior to 60 days after the cessation.
599 |
600 | Moreover, your license from a particular copyright holder is
601 | reinstated permanently if the copyright holder notifies you of the
602 | violation by some reasonable means, this is the first time you have
603 | received notice of violation of this License (for any work) from that
604 | copyright holder, and you cure the violation prior to 30 days after
605 | your receipt of the notice.
606 |
607 | Termination of your rights under this section does not terminate the
608 | licenses of parties who have received copies or rights from you under
609 | this License. If your rights have been terminated and not permanently
610 | reinstated, you do not qualify to receive new licenses for the same
611 | material under section 10.
612 |
613 | 9. Acceptance Not Required for Having Copies.
614 |
615 | You are not required to accept this License in order to receive or
616 | run a copy of the Program. Ancillary propagation of a covered work
617 | occurring solely as a consequence of using peer-to-peer transmission
618 | to receive a copy likewise does not require acceptance. However,
619 | nothing other than this License grants you permission to propagate or
620 | modify any covered work. These actions infringe copyright if you do
621 | not accept this License. Therefore, by modifying or propagating a
622 | covered work, you indicate your acceptance of this License to do so.
623 |
624 | 10. Automatic Licensing of Downstream Recipients.
625 |
626 | Each time you convey a covered work, the recipient automatically
627 | receives a license from the original licensors, to run, modify and
628 | propagate that work, subject to this License. You are not responsible
629 | for enforcing compliance by third parties with this License.
630 |
631 | An "entity transaction" is a transaction transferring control of an
632 | organization, or substantially all assets of one, or subdividing an
633 | organization, or merging organizations. If propagation of a covered
634 | work results from an entity transaction, each party to that
635 | transaction who receives a copy of the work also receives whatever
636 | licenses to the work the party's predecessor in interest had or could
637 | give under the previous paragraph, plus a right to possession of the
638 | Corresponding Source of the work from the predecessor in interest, if
639 | the predecessor has it or can get it with reasonable efforts.
640 |
641 | You may not impose any further restrictions on the exercise of the
642 | rights granted or affirmed under this License. For example, you may
643 | not impose a license fee, royalty, or other charge for exercise of
644 | rights granted under this License, and you may not initiate litigation
645 | (including a cross-claim or counterclaim in a lawsuit) alleging that
646 | any patent claim is infringed by making, using, selling, offering for
647 | sale, or importing the Program or any portion of it.
648 |
649 | 11. Patents.
650 |
651 | A "contributor" is a copyright holder who authorizes use under this
652 | License of the Program or a work on which the Program is based. The
653 | work thus licensed is called the contributor's "contributor version".
654 |
655 | A contributor's "essential patent claims" are all patent claims
656 | owned or controlled by the contributor, whether already acquired or
657 | hereafter acquired, that would be infringed by some manner, permitted
658 | by this License, of making, using, or selling its contributor version,
659 | but do not include claims that would be infringed only as a
660 | consequence of further modification of the contributor version. For
661 | purposes of this definition, "control" includes the right to grant
662 | patent sublicenses in a manner consistent with the requirements of
663 | this License.
664 |
665 | Each contributor grants you a non-exclusive, worldwide, royalty-free
666 | patent license under the contributor's essential patent claims, to
667 | make, use, sell, offer for sale, import and otherwise run, modify and
668 | propagate the contents of its contributor version.
669 |
670 | In the following three paragraphs, a "patent license" is any express
671 | agreement or commitment, however denominated, not to enforce a patent
672 | (such as an express permission to practice a patent or covenant not to
673 | sue for patent infringement). To "grant" such a patent license to a
674 | party means to make such an agreement or commitment not to enforce a
675 | patent against the party.
676 |
677 | If you convey a covered work, knowingly relying on a patent license,
678 | and the Corresponding Source of the work is not available for anyone
679 | to copy, free of charge and under the terms of this License, through a
680 | publicly available network server or other readily accessible means,
681 | then you must either (1) cause the Corresponding Source to be so
682 | available, or (2) arrange to deprive yourself of the benefit of the
683 | patent license for this particular work, or (3) arrange, in a manner
684 | consistent with the requirements of this License, to extend the patent
685 | license to downstream recipients. "Knowingly relying" means you have
686 | actual knowledge that, but for the patent license, your conveying the
687 | covered work in a country, or your recipient's use of the covered work
688 | in a country, would infringe one or more identifiable patents in that
689 | country that you have reason to believe are valid.
690 |
691 | If, pursuant to or in connection with a single transaction or
692 | arrangement, you convey, or propagate by procuring conveyance of, a
693 | covered work, and grant a patent license to some of the parties
694 | receiving the covered work authorizing them to use, propagate, modify
695 | or convey a specific copy of the covered work, then the patent license
696 | you grant is automatically extended to all recipients of the covered
697 | work and works based on it.
698 |
699 | A patent license is "discriminatory" if it does not include within
700 | the scope of its coverage, prohibits the exercise of, or is
701 | conditioned on the non-exercise of one or more of the rights that are
702 | specifically granted under this License. You may not convey a covered
703 | work if you are a party to an arrangement with a third party that is
704 | in the business of distributing software, under which you make payment
705 | to the third party based on the extent of your activity of conveying
706 | the work, and under which the third party grants, to any of the
707 | parties who would receive the covered work from you, a discriminatory
708 | patent license (a) in connection with copies of the covered work
709 | conveyed by you (or copies made from those copies), or (b) primarily
710 | for and in connection with specific products or compilations that
711 | contain the covered work, unless you entered into that arrangement,
712 | or that patent license was granted, prior to 28 March 2007.
713 |
714 | Nothing in this License shall be construed as excluding or limiting
715 | any implied license or other defenses to infringement that may
716 | otherwise be available to you under applicable patent law.
717 |
718 | 12. No Surrender of Others' Freedom.
719 |
720 | If conditions are imposed on you (whether by court order, agreement or
721 | otherwise) that contradict the conditions of this License, they do not
722 | excuse you from the conditions of this License. If you cannot convey a
723 | covered work so as to satisfy simultaneously your obligations under this
724 | License and any other pertinent obligations, then as a consequence you may
725 | not convey it at all. For example, if you agree to terms that obligate you
726 | to collect a royalty for further conveying from those to whom you convey
727 | the Program, the only way you could satisfy both those terms and this
728 | License would be to refrain entirely from conveying the Program.
729 |
730 | 13. Use with the GNU Affero General Public License.
731 |
732 | Notwithstanding any other provision of this License, you have
733 | permission to link or combine any covered work with a work licensed
734 | under version 3 of the GNU Affero General Public License into a single
735 | combined work, and to convey the resulting work. The terms of this
736 | License will continue to apply to the part which is the covered work,
737 | but the special requirements of the GNU Affero General Public License,
738 | section 13, concerning interaction through a network will apply to the
739 | combination as such.
740 |
741 | 14. Revised Versions of this License.
742 |
743 | The Free Software Foundation may publish revised and/or new versions of
744 | the GNU General Public License from time to time. Such new versions will
745 | be similar in spirit to the present version, but may differ in detail to
746 | address new problems or concerns.
747 |
748 | Each version is given a distinguishing version number. If the
749 | Program specifies that a certain numbered version of the GNU General
750 | Public License "or any later version" applies to it, you have the
751 | option of following the terms and conditions either of that numbered
752 | version or of any later version published by the Free Software
753 | Foundation. If the Program does not specify a version number of the
754 | GNU General Public License, you may choose any version ever published
755 | by the Free Software Foundation.
756 |
757 | If the Program specifies that a proxy can decide which future
758 | versions of the GNU General Public License can be used, that proxy's
759 | public statement of acceptance of a version permanently authorizes you
760 | to choose that version for the Program.
761 |
762 | Later license versions may give you additional or different
763 | permissions. However, no additional obligations are imposed on any
764 | author or copyright holder as a result of your choosing to follow a
765 | later version.
766 |
767 | 15. Disclaimer of Warranty.
768 |
769 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
770 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
771 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
772 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
773 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
774 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
775 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
776 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
777 |
778 | 16. Limitation of Liability.
779 |
780 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
781 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
782 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
783 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
784 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
785 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
786 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
787 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
788 | SUCH DAMAGES.
789 |
790 | 17. Interpretation of Sections 15 and 16.
791 |
792 | If the disclaimer of warranty and limitation of liability provided
793 | above cannot be given local legal effect according to their terms,
794 | reviewing courts shall apply local law that most closely approximates
795 | an absolute waiver of all civil liability in connection with the
796 | Program, unless a warranty or assumption of liability accompanies a
797 | copy of the Program in return for a fee.
798 |
799 | END OF TERMS AND CONDITIONS
800 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include COPYING.txt
2 | include README.txt
3 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | Note: this repository is no longer maintained!
2 |
3 | Please see the fork by Danielhiversen for a more up to date version!
4 | https://github.com/Danielhiversen/pyRFXtrx
5 |
6 | ========
7 | pyRFXtrx
8 | ========
9 |
10 | A Python library to communicate with the RFXtrx family of devices
11 | from http://www.rfxcom.com/
12 |
13 | See https://github.com/woudt/pyRFXtrx for the latest version.
14 |
15 |
16 | Using
17 | =====
18 |
19 | Install pySerial first::
20 | $ sudo easy_install -U pyserial
21 |
22 |
23 | After that, see the examples in the examples directory
24 |
25 |
26 | Developers
27 | ==========
28 |
29 | To run the test scripts::
30 | $ ./test_run.sh
31 |
32 | To run the test scripts for all supported Python versions (requires a proper
33 | environment, with all Python versions (2.6, 2.7, 3.1, 3.2 and 3.3) installed)::
34 | $ doctest/all_versions.sh
35 |
36 | Run pylint and pep8 checks on the source code:
37 | $ sudo easy_install -U pep8 logilab-common logilab-astng pylint
38 | $ ./lint_run.sh
39 |
40 |
41 | Licensing
42 | =========
43 |
44 | Copyright (C) 2012 Edwin Woudt
45 |
46 | pyRFXtrx is free software: you can redistribute it and/or modify it
47 | under the terms of the GNU Lesser General Public License as published
48 | by the Free Software Foundation, either version 3 of the License, or
49 | (at your option) any later version.
50 |
51 | pyRFXtrx is distributed in the hope that it will be useful,
52 | but WITHOUT ANY WARRANTY; without even the implied warranty of
53 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
54 | GNU Lesser General Public License for more details.
55 |
56 | You should have received a copy of the GNU Lesser General Public License
57 | along with pyRFXtrx. See the file COPYING.txt in the distribution.
58 | If not, see .
59 |
--------------------------------------------------------------------------------
/RFXtrx/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of pyRFXtrx, a Python library to communicate with
2 | # the RFXtrx family of devices from http://www.rfxcom.com/
3 | # See https://github.com/woudt/pyRFXtrx for the latest version.
4 | #
5 | # Copyright (C) 2012 Edwin Woudt
6 | #
7 | # pyRFXtrx is free software: you can redistribute it and/or modify it
8 | # under the terms of the GNU Lesser General Public License as published
9 | # by the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # pyRFXtrx is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public License
18 | # along with pyRFXtrx. See the file COPYING.txt in the distribution.
19 | # If not, see .
20 | """
21 | This module provides the base implementation for pyRFXtrx
22 | """
23 | # pylint: disable=R0903
24 |
25 | from RFXtrx import lowlevel
26 |
27 |
28 | ###############################################################################
29 | # RFXtrxTransport class
30 | ###############################################################################
31 |
32 | class RFXtrxTransport(object):
33 | """ Abstract superclass for all transport mechanisms """
34 |
35 | @staticmethod
36 | def parse(data):
37 | """ Parse the given data and return an RFXtrxEvent """
38 | pkt = lowlevel.parse(data)
39 | if pkt is not None:
40 | if isinstance(pkt, lowlevel.SensorPacket):
41 | return SensorEvent(pkt)
42 | elif isinstance(pkt, lowlevel.Status):
43 | return StatusEvent(pkt)
44 | else:
45 | return ControlEvent(pkt)
46 |
47 |
48 | ###############################################################################
49 | # RFXtrxDevice class
50 | ###############################################################################
51 |
52 | class RFXtrxDevice(object):
53 | """ Superclass for all devices """
54 |
55 | def __init__(self, pkt):
56 | self.packettype = pkt.packettype
57 | self.subtype = pkt.subtype
58 | self.type_string = pkt.type_string
59 | self.id_string = pkt.id_string
60 |
61 | def __eq__(self, other):
62 | if self.packettype != other.packettype:
63 | return False
64 | if self.subtype != other.subtype:
65 | return False
66 | return self.id_string == other.id_string
67 |
68 | def __str__(self):
69 | return "{0} type='{1}' id='{2}'".format(
70 | type(self), self.type_string, self.id_string)
71 |
72 |
73 | ###############################################################################
74 | # LightingDevice class
75 | ###############################################################################
76 |
77 | class LightingDevice(RFXtrxDevice):
78 | """ Concrete class for a lighting device """
79 |
80 | def __init__(self, pkt):
81 | super(LightingDevice, self).__init__(pkt)
82 | if isinstance(pkt, lowlevel.Lighting1):
83 | self.housecode = pkt.housecode
84 | self.unitcode = pkt.unitcode
85 | if isinstance(pkt, lowlevel.Lighting2):
86 | self.id_combined = pkt.id_combined
87 | self.unitcode = pkt.unitcode
88 | if isinstance(pkt, lowlevel.Lighting3):
89 | self.system = pkt.system
90 | self.channel = pkt.channel
91 | if isinstance(pkt, lowlevel.Lighting5):
92 | self.id_combined = pkt.id_combined
93 | self.unitcode = pkt.unitcode
94 | if isinstance(pkt, lowlevel.Lighting6):
95 | self.id_combined = pkt.id_combined
96 | self.groupcode = pkt.groupcode
97 | self.unitcode = pkt.unitcode
98 | self.cmndseqnbr = 0
99 |
100 | def send_onoff(self, transport, on):
101 | """ Send an 'On' or 'Off' command using the given transport """
102 | if self.packettype == 0x10: # Lighting1
103 | pkt = lowlevel.Lighting1()
104 | pkt.set_transmit(self.subtype, 0, self.housecode, self.unitcode,
105 | on and 0x01 or 0x00)
106 | transport.send(pkt.data)
107 | elif self.packettype == 0x11: # Lighting2
108 | pkt = lowlevel.Lighting2()
109 | pkt.set_transmit(self.subtype, 0, self.id_combined, self.unitcode,
110 | on and 0x01 or 0x00, 0x00)
111 | transport.send(pkt.data)
112 | elif self.packettype == 0x12: # Lighting3
113 | pkt = lowlevel.Lighting3()
114 | pkt.set_transmit(self.subtype, 0, self.system, self.channel,
115 | on and 0x10 or 0x1a)
116 | transport.send(pkt.data)
117 | elif self.packettype == 0x14: # Lighting5
118 | pkt = lowlevel.Lighting5()
119 | pkt.set_transmit(self.subtype, 0, self.id_combined, self.unitcode,
120 | on and 0x01 or 0x00, 0x00)
121 | transport.send(pkt.data)
122 | elif self.packettype == 0x15: # Lighting6
123 | pkt = lowlevel.Lighting6()
124 | pkt.set_transmit(self.subtype, 0, self.id_combined, self.groupcode,
125 | self.unitcode, not on and 0x01 or 0x00, self.cmndseqnbr)
126 | self.cmndseqnbr = (self.cmndseqnbr + 1) % 5
127 | transport.send(pkt.data)
128 | else:
129 | raise ValueError("Unsupported packettype")
130 |
131 | def send_on(self, transport):
132 | """ Send an 'On' command using the given transport """
133 | self.send_onoff(transport, True)
134 |
135 | def send_off(self, transport):
136 | """ Send an 'Off' command using the given transport """
137 | self.send_onoff(transport, False)
138 |
139 | def send_dim(self, transport, level):
140 | """ Send a 'Dim' command with the given level using the given
141 | transport
142 | """
143 | if self.packettype == 0x10: # Lighting1
144 | raise ValueError("Dim level unsupported for Lighting1")
145 | # Supporting a dim level for X10 directly is not possible because
146 | # RFXtrx does not support sending extended commands
147 | elif self.packettype == 0x11: # Lighting2
148 | if level == 0:
149 | self.send_off(transport)
150 | else:
151 | pkt = lowlevel.Lighting2()
152 | pkt.set_transmit(self.subtype, 0, self.id_combined,
153 | self.unitcode, 0x02,
154 | ((level + 6) * 16 // 100) - 1)
155 | transport.send(pkt.data)
156 | elif self.packettype == 0x12: # Lighting3
157 | raise ValueError("Dim level unsupported for Lighting3")
158 | # Should not be too hard to add dim level support for Lighting3
159 | # (Ikea Koppla) due to the availability of the level 1 .. level 9
160 | # commands. I just need someone to help me with defining a mapping
161 | # between a percentage and a level
162 | elif self.packettype == 0x14: # Lighting5
163 | if level == 0:
164 | self.send_off(transport)
165 | else:
166 | pkt = lowlevel.Lighting5()
167 | pkt.set_transmit(self.subtype, 0, self.id_combined,
168 | self.unitcode, 0x10,
169 | ((level + 3) * 32 // 100) - 1)
170 | transport.send(pkt.data)
171 | elif self.packettype == 0x15: # Lighting6
172 | raise ValueError("Dim level unsupported for Lighting6")
173 | else:
174 | raise ValueError("Unsupported packettype")
175 |
176 |
177 | ###############################################################################
178 | # get_devide method
179 | ###############################################################################
180 |
181 | def get_device(packettype, subtype, id_string):
182 | """ Return a device base on its identifying values """
183 | if packettype == 0x10: # Lighting1
184 | pkt = lowlevel.Lighting1()
185 | pkt.parse_id(subtype, id_string)
186 | return LightingDevice(pkt)
187 | elif packettype == 0x11: # Lighting2
188 | pkt = lowlevel.Lighting2()
189 | pkt.parse_id(subtype, id_string)
190 | return LightingDevice(pkt)
191 | elif packettype == 0x12: # Lighting3
192 | pkt = lowlevel.Lighting3()
193 | pkt.parse_id(subtype, id_string)
194 | return LightingDevice(pkt)
195 | elif packettype == 0x14: # Lighting5
196 | pkt = lowlevel.Lighting5()
197 | pkt.parse_id(subtype, id_string)
198 | return LightingDevice(pkt)
199 | elif packettype == 0x15: # Lighting6
200 | pkt = lowlevel.Lighting6()
201 | pkt.parse_id(subtype, id_string)
202 | return LightingDevice(pkt)
203 | else:
204 | raise ValueError("Unsupported packettype")
205 |
206 |
207 | ###############################################################################
208 | # RFXtrxEvent class
209 | ###############################################################################
210 |
211 | class RFXtrxEvent(object):
212 | """ Abstract superclass for all events """
213 |
214 | def __init__(self, device):
215 | self.device = device
216 |
217 |
218 | ###############################################################################
219 | # SensorEvent class
220 | ###############################################################################
221 |
222 | class SensorEvent(RFXtrxEvent):
223 | """ Concrete class for sensor events """
224 |
225 | def __init__(self, pkt):
226 | device = RFXtrxDevice(pkt)
227 | super(SensorEvent, self).__init__(device)
228 |
229 | self.values = {}
230 | if isinstance(pkt, lowlevel.Temp) \
231 | or isinstance(pkt, lowlevel.TempHumid) \
232 | or isinstance(pkt, lowlevel.TempHumidBaro):
233 | self.values['Temperature'] = pkt.temp
234 | if isinstance(pkt, lowlevel.Humid) \
235 | or isinstance(pkt, lowlevel.TempHumid) \
236 | or isinstance(pkt, lowlevel.TempHumidBaro):
237 | self.values['Humidity'] = pkt.humidity
238 | self.values['Humidity status'] = pkt.humidity_status_string
239 | self.values['Humidity status numeric'] = pkt.humidity_status
240 | if isinstance(pkt, lowlevel.Baro) \
241 | or isinstance(pkt, lowlevel.TempHumidBaro):
242 | self.values['Barometer'] = pkt.baro
243 | self.values['Forecast'] = pkt.forecast_string
244 | self.values['Forecast numeric'] = pkt.forecast
245 | if isinstance(pkt, lowlevel.Rain):
246 | self.values['Rain rate'] = pkt.rainrate
247 | self.values['Rain total'] = pkt.raintotal
248 | if isinstance(pkt, lowlevel.Wind):
249 | self.values['Wind direction'] = pkt.direction
250 | self.values['Wind average speed'] = pkt.average_speed
251 | self.values['Wind gust'] = pkt.gust
252 | self.values['Temperature'] = pkt.temperature
253 | self.values['Chill'] = pkt.chill
254 | self.values['Battery numeric'] = pkt.battery
255 | self.values['Rssi numeric'] = pkt.rssi
256 |
257 | def __str__(self):
258 | return "{0} device=[{1}] values={2}".format(
259 | type(self), self.device, sorted(self.values.items()))
260 |
261 |
262 | ###############################################################################
263 | # ControlEvent class
264 | ###############################################################################
265 |
266 | class ControlEvent(RFXtrxEvent):
267 | """ Concrete class for control events """
268 |
269 | def __init__(self, pkt):
270 | if isinstance(pkt, lowlevel.Lighting1) \
271 | or isinstance(pkt, lowlevel.Lighting2) \
272 | or isinstance(pkt, lowlevel.Lighting3) \
273 | or isinstance(pkt, lowlevel.Lighting5) \
274 | or isinstance(pkt, lowlevel.Lighting6):
275 | device = LightingDevice(pkt)
276 | else:
277 | device = RFXtrxDevice(pkt)
278 | super(ControlEvent, self).__init__(device)
279 |
280 | self.values = {}
281 | if isinstance(pkt, lowlevel.Lighting1) \
282 | or isinstance(pkt, lowlevel.Lighting2) \
283 | or isinstance(pkt, lowlevel.Lighting3):
284 | self.values['Command'] = pkt.cmnd_string
285 | if isinstance(pkt, lowlevel.Lighting2) and pkt.cmnd in [2, 5]:
286 | self.values['Dim level'] = (pkt.level + 1) * 100 // 16
287 | if isinstance(pkt, lowlevel.Lighting5) and pkt.cmnd in [0x10]:
288 | self.values['Dim level'] = (pkt.level + 1) * 100 // 32
289 | self.values['Rssi numeric'] = pkt.rssi
290 |
291 | def __str__(self):
292 | return "{0} device=[{1}] values={2}".format(
293 | type(self), self.device, sorted(self.values.items()))
294 |
295 | ###############################################################################
296 | # Status class
297 | ###############################################################################
298 |
299 | class StatusEvent(RFXtrxEvent):
300 | """ Concrete class for status """
301 |
302 | def __init__(self, pkt):
303 | super(StatusEvent, self).__init__(pkt)
304 |
305 | def __str__(self):
306 | return "{0} device=[{1}]".format(
307 | type(self), self.device)
308 |
--------------------------------------------------------------------------------
/RFXtrx/dummy.py:
--------------------------------------------------------------------------------
1 | # This file is part of pyRFXtrx, a Python library to communicate with
2 | # the RFXtrx family of devices from http://www.rfxcom.com/
3 | # See https://github.com/woudt/pyRFXtrx for the latest version.
4 | #
5 | # Copyright (C) 2012 Edwin Woudt
6 | #
7 | # pyRFXtrx is free software: you can redistribute it and/or modify it
8 | # under the terms of the GNU Lesser General Public License as published
9 | # by the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # pyRFXtrx is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public License
18 | # along with pyRFXtrx. See the file COPYING.txt in the distribution.
19 | # If not, see .
20 | """
21 | This module provides a dummy transport for testing purposes
22 | """
23 |
24 | from RFXtrx import RFXtrxTransport
25 |
26 |
27 | class DummyTransport(RFXtrxTransport):
28 | """ Dummy transport for testing purposes """
29 |
30 | def __init__(self, debug=True):
31 | self.debug = debug
32 |
33 | def receive(self, data):
34 | """ Emulate a receive by parsing the given data """
35 | pkt = bytearray(data)
36 | if self.debug:
37 | print ("Recv: " + " ".join("0x{0:02x}".format(x) for x in pkt))
38 | return self.parse(pkt)
39 |
40 | def send(self, data):
41 | """ Emulate a send by doing nothing (except printing debug info if
42 | requested) """
43 | pkt = bytearray(data)
44 | if self.debug:
45 | print ("Send: " + " ".join("0x{0:02x}".format(x) for x in pkt))
46 |
--------------------------------------------------------------------------------
/RFXtrx/lowlevel.py:
--------------------------------------------------------------------------------
1 | # This file is part of pyRFXtrx, a Python library to communicate with
2 | # the RFXtrx family of devices from http://www.rfxcom.com/
3 | # See https://github.com/woudt/pyRFXtrx for the latest version.
4 | #
5 | # Copyright (C) 2012 Edwin Woudt
6 | #
7 | # pyRFXtrx is free software: you can redistribute it and/or modify it
8 | # under the terms of the GNU Lesser General Public License as published
9 | # by the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # pyRFXtrx is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public License
18 | # along with pyRFXtrx. See the file COPYING.txt in the distribution.
19 | # If not, see .
20 | """
21 | This module provides low level packet parsing and generation code for the
22 | RFXtrx.
23 | """
24 | # pylint: disable=C0302,R0902,R0903,R0911,R0913
25 |
26 |
27 | def parse(data):
28 | """ Parse a packet from a bytearray """
29 | if data[0] == 0:
30 | # null length packet - sometimes happens on initialization
31 | return None
32 | if data[1] == 0x01:
33 | pkt = Status()
34 | pkt.load_receive(data)
35 | return pkt
36 | if data[1] == 0x10:
37 | pkt = Lighting1()
38 | pkt.load_receive(data)
39 | return pkt
40 | if data[1] == 0x11:
41 | pkt = Lighting2()
42 | pkt.load_receive(data)
43 | return pkt
44 | if data[1] == 0x12:
45 | pkt = Lighting3()
46 | pkt.load_receive(data)
47 | return pkt
48 | if data[1] == 0x13:
49 | pkt = Lighting4()
50 | pkt.load_receive(data)
51 | return pkt
52 | if data[1] == 0x14:
53 | pkt = Lighting5()
54 | pkt.load_receive(data)
55 | return pkt
56 | if data[1] == 0x15:
57 | pkt = Lighting6()
58 | pkt.load_receive(data)
59 | return pkt
60 | if data[1] == 0x50:
61 | pkt = Temp()
62 | pkt.load_receive(data)
63 | return pkt
64 | if data[1] == 0x52:
65 | pkt = TempHumid()
66 | pkt.load_receive(data)
67 | return pkt
68 | if data[1] == 0x54:
69 | pkt = TempHumidBaro()
70 | pkt.load_receive(data)
71 | return pkt
72 | if data[1] == 0x55:
73 | pkt = RainGauge()
74 | pkt.load_receive(data)
75 | return pkt
76 | if data[1] == 0x56:
77 | pkt = Wind()
78 | pkt.load_receive(data)
79 | return pkt
80 |
81 |
82 | ###############################################################################
83 | # Packet class
84 | ###############################################################################
85 |
86 | class Packet(object):
87 | """ Abstract superclass for all low level packets """
88 |
89 | _UNKNOWN_TYPE = "Unknown type ({0:#04x}/{1:#04x})"
90 | _UNKNOWN_CMND = "Unknown command ({0:#04x})"
91 |
92 | def __init__(self):
93 | """Constructor"""
94 | self.data = None
95 | self.packetlength = None
96 | self.packettype = None
97 | self.subtype = None
98 | self.seqnbr = None
99 | self.rssi = None
100 | self.rssi_byte = None
101 | self.type_string = None
102 | self.id_string = None
103 |
104 |
105 | ###############################################################################
106 | # Status class
107 | ###############################################################################
108 |
109 | def _decode_flags(v, words):
110 | words = words.split()
111 | s = set()
112 | for w in words:
113 | if v % 2:
114 | s.add(w)
115 | v//= 2
116 | return s
117 |
118 | class Status(Packet):
119 | """
120 | Data class for the Status packet type
121 | """
122 |
123 | TYPES = {
124 | 0x50: '310MHz',
125 | 0x51: '315MHz',
126 | 0x53: '433.92MHz',
127 | 0x55: '868.00MHz',
128 | 0x56: '868.00MHz FSK',
129 | 0x57: '868.30MHz',
130 | 0x58: '868.30MHz FSK',
131 | 0x59: '868.35MHz',
132 | 0x5A: '868.35MHz FSK',
133 | 0x5B: '868.95MHz'
134 | }
135 | """
136 | Mapping of numeric subtype values to strings, used in type_string
137 | """
138 |
139 | def __str__(self):
140 | return ("Status [subtype={0}, firmware={1}, devices={2}]") \
141 | .format(self.type_string, self.firmware_version, self.devices)
142 |
143 | def __init__(self):
144 | """Constructor"""
145 | super(Status, self).__init__()
146 | self.tranceiver_type = None
147 | self.firmware_version = None
148 | self.devices = None
149 |
150 | def load_receive(self, data):
151 | """Load data from a bytearray"""
152 | self.data = data
153 | self.packetlength = data[0]
154 | self.packettype = data[1]
155 |
156 | self.tranceiver_type = data[5]
157 | self.firmware_version = data[6]
158 |
159 | devs = set()
160 | devs.update(_decode_flags(data[7] / 0x80,
161 | 'undecoded'))
162 | devs.update(_decode_flags(data[8],
163 | 'mertik lightwarerf hideki lacrosse fs20 proguard'))
164 | devs.update(_decode_flags(data[9],
165 | 'x10 arc ac homeeasy ikeakoppla oregon ati visonic'))
166 | self.devices = sorted(devs)
167 |
168 | self._set_strings()
169 |
170 | def _set_strings(self):
171 | """Translate loaded numeric values into convenience strings"""
172 | if self.tranceiver_type in self.TYPES:
173 | self.type_string = self.TYPES[self.tranceiver_type]
174 | else:
175 | #Degrade nicely for yet unknown subtypes
176 | self.type_string = 'Unknown'
177 |
178 |
179 | ###############################################################################
180 | # Lighting1 class
181 | ###############################################################################
182 |
183 | class Lighting1(Packet):
184 | """
185 | Data class for the Lighting1 packet type
186 | """
187 |
188 | TYPES = {0x00: 'X10 lighting',
189 | 0x01: 'ARC',
190 | 0x02: 'ELRO AB400D',
191 | 0x03: 'Waveman',
192 | 0x04: 'Chacon EMW200',
193 | 0x05: 'IMPULS',
194 | 0x06: 'RisingSun',
195 | 0x07: 'Philips SBC',
196 | 0x08: 'Energenie',
197 | }
198 | """
199 | Mapping of numeric subtype values to strings, used in type_string
200 | """
201 |
202 | ALIAS_TYPES = {'KlikAanKlikUit code wheel': 0x01,
203 | 'NEXA code wheel': 0x01,
204 | 'CHACON code wheel': 0x01,
205 | 'HomeEasy code wheel': 0x01,
206 | 'Proove': 0x01,
207 | 'DomiaLite': 0x01,
208 | 'InterTechno': 0x01,
209 | 'AB600': 0x01,
210 | }
211 | """
212 | Mapping of subtype aliases to the corresponding subtype value
213 | """
214 |
215 | HOUSECODES = {0x41: 'A', 0x42: 'B', 0x43: 'C', 0x44: 'D',
216 | 0x45: 'E', 0x46: 'F', 0x47: 'G', 0x48: 'H',
217 | 0x49: 'I', 0x4A: 'J', 0x4B: 'K', 0x4C: 'L',
218 | 0x4D: 'M', 0x4E: 'N', 0x4F: 'O', 0x50: 'P'}
219 | """
220 | Mapping of housecode numeric values to strings, used in id_string
221 | """
222 |
223 | COMMANDS = {0x00: 'Off',
224 | 0x01: 'On',
225 | 0x02: 'Dim',
226 | 0x03: 'Bright',
227 | 0x05: 'All/group Off',
228 | 0x06: 'All/group On',
229 | 0x07: 'Chime',
230 | 0xFF: 'Illegal command'}
231 | """
232 | Mapping of command numeric values to strings, used for cmnd_string
233 | """
234 |
235 | def __str__(self):
236 | return ("Lighting1 [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
237 | "rssi={4}]") \
238 | .format(self.type_string, self.seqnbr, self.id_string,
239 | self.cmnd_string, self.rssi)
240 |
241 | def __init__(self):
242 | """Constructor"""
243 | super(Lighting1, self).__init__()
244 | self.housecode = None
245 | self.unitcode = None
246 | self.cmnd = None
247 | self.cmnd_string = None
248 |
249 | def parse_id(self, subtype, id_string):
250 | """Parse a string id into individual components"""
251 | try:
252 | self.packettype = 0x10
253 | self.subtype = subtype
254 | hcode = id_string[0:1]
255 | for hcode_num in self.HOUSECODES:
256 | if self.HOUSECODES[hcode_num] == hcode:
257 | self.housecode = hcode_num
258 | self.unitcode = int(id_string[1:])
259 | self._set_strings()
260 | except:
261 | raise ValueError("Invalid id_string")
262 | if self.id_string != id_string:
263 | raise ValueError("Invalid id_string")
264 |
265 | def load_receive(self, data):
266 | """Load data from a bytearray"""
267 | self.data = data
268 | self.packetlength = data[0]
269 | self.packettype = data[1]
270 | self.subtype = data[2]
271 | self.seqnbr = data[3]
272 | self.housecode = data[4]
273 | self.unitcode = data[5]
274 | self.cmnd = data[6]
275 | self.rssi_byte = data[7]
276 | self.rssi = self.rssi_byte >> 4
277 | self._set_strings()
278 |
279 | def set_transmit(self, subtype, seqnbr, housecode, unitcode, cmnd):
280 | """Load data from individual data fields"""
281 | self.packetlength = 7
282 | self.packettype = 0x10
283 | self.subtype = subtype
284 | self.seqnbr = seqnbr
285 | self.housecode = housecode
286 | self.unitcode = unitcode
287 | self.cmnd = cmnd
288 | self.rssi_byte = 0
289 | self.rssi = 0
290 | self.data = bytearray([self.packetlength, self.packettype,
291 | self.subtype, self.seqnbr, self.housecode,
292 | self.unitcode, self.cmnd, self.rssi_byte])
293 | self._set_strings()
294 |
295 | def _set_strings(self):
296 | """Translate loaded numeric values into convenience strings"""
297 | self.id_string = self.HOUSECODES[self.housecode] + str(self.unitcode)
298 | if self.subtype in self.TYPES:
299 | self.type_string = self.TYPES[self.subtype]
300 | else:
301 | #Degrade nicely for yet unknown subtypes
302 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
303 | self.subtype)
304 | if self.cmnd is not None:
305 | if self.cmnd in self.COMMANDS:
306 | self.cmnd_string = self.COMMANDS[self.cmnd]
307 | else:
308 | self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
309 |
310 |
311 | ###############################################################################
312 | # Lighting2 class
313 | ###############################################################################
314 |
315 | class Lighting2(Packet):
316 | """
317 | Data class for the Lighting2 packet type
318 | """
319 |
320 | TYPES = {0x00: 'AC',
321 | 0x01: 'HomeEasy EU',
322 | 0x02: 'ANSLUT',
323 | }
324 | """
325 | Mapping of numeric subtype values to strings, used in type_string
326 | """
327 |
328 | ALIAS_TYPES = {'KlikAanKlikUit automatic': 0x00,
329 | 'NEXA automatic': 0x00,
330 | 'CHACON autometic': 0x00,
331 | 'HomeEasy UK': 0x00,
332 | }
333 | """
334 | Mapping of subtype aliases to the corresponding subtype value
335 | """
336 |
337 | COMMANDS = {0x00: 'Off',
338 | 0x01: 'On',
339 | 0x02: 'Set level',
340 | 0x03: 'Group off',
341 | 0x04: 'Group on',
342 | 0x05: 'Set group level',
343 | }
344 | """
345 | Mapping of command numeric values to strings, used for cmnd_string
346 | """
347 |
348 | def __str__(self):
349 | return ("Lighting2 [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
350 | "level={4}, rssi={5}]") \
351 | .format(self.type_string, self.seqnbr, self.id_string,
352 | self.cmnd_string, self.level, self.rssi)
353 |
354 | def __init__(self):
355 | """Constructor"""
356 | super(Lighting2, self).__init__()
357 | self.id1 = None
358 | self.id2 = None
359 | self.id3 = None
360 | self.id4 = None
361 | self.id_combined = None
362 | self.unitcode = None
363 | self.cmnd = None
364 | self.level = None
365 | self.cmnd_string = None
366 |
367 | def parse_id(self, subtype, id_string):
368 | """Parse a string id into individual components"""
369 | try:
370 | self.packettype = 0x11
371 | self.subtype = subtype
372 | self.id_combined = int(id_string[:7], 16)
373 | self.id1 = self.id_combined >> 24
374 | self.id2 = self.id_combined >> 16 & 0xff
375 | self.id3 = self.id_combined >> 8 & 0xff
376 | self.id4 = self.id_combined & 0xff
377 | self.unitcode = int(id_string[8:])
378 | self._set_strings()
379 | except:
380 | raise ValueError("Invalid id_string")
381 | if self.id_string != id_string:
382 | raise ValueError("Invalid id_string")
383 |
384 | def load_receive(self, data):
385 | """Load data from a bytearray"""
386 | self.data = data
387 | self.packetlength = data[0]
388 | self.packettype = data[1]
389 | self.subtype = data[2]
390 | self.seqnbr = data[3]
391 | self.id1 = data[4]
392 | self.id2 = data[5]
393 | self.id3 = data[6]
394 | self.id4 = data[7]
395 | self.id_combined = (self.id1 << 24) + (self.id2 << 16) \
396 | + (self.id3 << 8) + self.id4
397 | self.unitcode = data[8]
398 | self.cmnd = data[9]
399 | self.level = data[10]
400 | self.rssi_byte = data[11]
401 | self.rssi = self.rssi_byte >> 4
402 | self._set_strings()
403 |
404 | def set_transmit(self, subtype, seqnbr, id_combined, unitcode, cmnd,
405 | level):
406 | """Load data from individual data fields"""
407 | self.packetlength = 0x0b
408 | self.packettype = 0x11
409 | self.subtype = subtype
410 | self.seqnbr = seqnbr
411 | self.id_combined = id_combined
412 | self.id1 = id_combined >> 24
413 | self.id2 = id_combined >> 16 & 0xff
414 | self.id3 = id_combined >> 8 & 0xff
415 | self.id4 = id_combined & 0xff
416 | self.unitcode = unitcode
417 | self.cmnd = cmnd
418 | self.level = level
419 | self.rssi_byte = 0
420 | self.rssi = 0
421 | self.data = bytearray([self.packetlength, self.packettype,
422 | self.subtype, self.seqnbr, self.id1, self.id2,
423 | self.id3, self.id4, self.unitcode, self.cmnd,
424 | self.level, self.rssi_byte])
425 | self._set_strings()
426 |
427 | def _set_strings(self):
428 | """Translate loaded numeric values into convenience strings"""
429 | self.id_string = "{0:07x}:{1}".format(self.id_combined, self.unitcode)
430 | if self.subtype in self.TYPES:
431 | self.type_string = self.TYPES[self.subtype]
432 | else:
433 | #Degrade nicely for yet unknown subtypes
434 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
435 | self.subtype)
436 | if self.cmnd is not None:
437 | if self.cmnd in self.COMMANDS:
438 | self.cmnd_string = self.COMMANDS[self.cmnd]
439 | else:
440 | self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
441 |
442 |
443 | ###############################################################################
444 | # Lighting3 class
445 | ###############################################################################
446 |
447 | class Lighting3(Packet):
448 | """
449 | Data class for the Lighting3 packet type
450 | """
451 |
452 | TYPES = {0x00: 'Ikea Koppla',
453 | }
454 | """
455 | Mapping of numeric subtype values to strings, used in type_string
456 | """
457 |
458 | COMMANDS = {0x00: 'Bright',
459 | 0x08: 'Dim',
460 | 0x10: 'On',
461 | 0x11: 'Level 1',
462 | 0x12: 'Level 2',
463 | 0x13: 'Level 3',
464 | 0x14: 'Level 4',
465 | 0x15: 'Level 5',
466 | 0x16: 'Level 6',
467 | 0x17: 'Level 7',
468 | 0x18: 'Level 8',
469 | 0x19: 'Level 9',
470 | 0x1a: 'Off',
471 | 0x1c: 'Program',
472 | }
473 | """
474 | Mapping of command numeric values to strings, used for cmnd_string
475 | """
476 |
477 | def __str__(self):
478 | return ("Lighting3 [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
479 | "battery={4}, rssi={5}]") \
480 | .format(self.type_string, self.seqnbr, self.id_string,
481 | self.cmnd_string, self.battery, self.rssi)
482 |
483 | def __init__(self):
484 | """Constructor"""
485 | super(Lighting3, self).__init__()
486 | self.system = None
487 | self.channel1 = None
488 | self.channel2 = None
489 | self.channel = None
490 | self.cmnd = None
491 | self.battery = None
492 | self.cmnd_string = None
493 |
494 | def parse_id(self, subtype, id_string):
495 | """Parse a string id into individual components"""
496 | try:
497 | self.packettype = 0x12
498 | self.subtype = subtype
499 | self.system = int(id_string[:1], 16)
500 | self.channel = int(id_string[2:], 16)
501 | self.channel1 = self.channel & 0xff
502 | self.channel2 = self.channel >> 8
503 | self._set_strings()
504 | except:
505 | raise ValueError("Invalid id_string")
506 | if self.id_string != id_string:
507 | raise ValueError("Invalid id_string")
508 |
509 | def load_receive(self, data):
510 | """Load data from a bytearray"""
511 | self.data = data
512 | self.packetlength = data[0]
513 | self.packettype = data[1]
514 | self.subtype = data[2]
515 | self.seqnbr = data[3]
516 | self.system = data[4]
517 | self.channel1 = data[5]
518 | self.channel2 = data[6]
519 | self.channel = (self.channel2 << 8) + self.channel1
520 | self.cmnd = data[7]
521 | self.rssi_byte = data[8]
522 | self.battery = self.rssi_byte & 0x0f
523 | self.rssi = self.rssi_byte >> 4
524 | self._set_strings()
525 |
526 | def set_transmit(self, subtype, seqnbr, system, channel, cmnd):
527 | """Load data from individual data fields"""
528 | self.packetlength = 0x08
529 | self.packettype = 0x12
530 | self.subtype = subtype
531 | self.seqnbr = seqnbr
532 | self.system = system
533 | self.channel = channel
534 | self.channel1 = channel & 0xff
535 | self.channel2 = channel >> 8
536 | self.cmnd = cmnd
537 | self.rssi_byte = 0
538 | self.battery = 0
539 | self.rssi = 0
540 | self.data = bytearray([self.packetlength, self.packettype,
541 | self.subtype, self.seqnbr, self.system,
542 | self.channel1, self.channel2, self.cmnd,
543 | self.rssi_byte])
544 | self._set_strings()
545 |
546 | def _set_strings(self):
547 | """Translate loaded numeric values into convenience strings"""
548 | self.id_string = "{0:1x}:{1:03x}".format(self.system, self.channel)
549 | if self.subtype in self.TYPES:
550 | self.type_string = self.TYPES[self.subtype]
551 | else:
552 | #Degrade nicely for yet unknown subtypes
553 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
554 | self.subtype)
555 | if self.cmnd is not None:
556 | if self.cmnd in self.COMMANDS:
557 | self.cmnd_string = self.COMMANDS[self.cmnd]
558 | else:
559 | self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
560 |
561 |
562 | ###############################################################################
563 | # Lighting4 class
564 | ###############################################################################
565 |
566 | class Lighting4(Packet):
567 | """
568 | Data class for the Lighting4 packet type
569 | """
570 |
571 | TYPES = {0x00: 'PT2262',
572 | }
573 | """
574 | Mapping of numeric subtype values to strings, used in type_string
575 | """
576 |
577 | def __str__(self):
578 | return ("Lighting4 [subtype={0}, seqnbr={1}, cmd={2}, pulse={3}, " +
579 | "rssi={4}]") \
580 | .format(self.type_string, self.seqnbr, self.id_string,
581 | self.pulse, self.rssi)
582 |
583 | def __init__(self):
584 | """Constructor"""
585 | super(Lighting4, self).__init__()
586 | self.cmd1 = None
587 | self.cmd2 = None
588 | self.cmd3 = None
589 | self.cmd = None
590 | self.pulsehigh = None
591 | self.pulselow = None
592 | self.pulse = None
593 |
594 | def parse_id(self, subtype, id_string):
595 | """Parse a string id into individual components"""
596 | try:
597 | self.packettype = 0x13
598 | self.subtype = subtype
599 | self.cmd = int(id_string, 16)
600 | self.cmd1 = self.cmd >> 16
601 | self.cmd2 = (self.cmd >> 8) & 0xff
602 | self.cmd3 = self.cmd & 0xff
603 | self._set_strings()
604 | except:
605 | raise ValueError("Invalid id_string")
606 | if self.id_string != id_string:
607 | raise ValueError("Invalid id_string")
608 |
609 | def load_receive(self, data):
610 | """Load data from a bytearray"""
611 | self.data = data
612 | self.packetlength = data[0]
613 | self.packettype = data[1]
614 | self.subtype = data[2]
615 | self.seqnbr = data[3]
616 | self.cmd1 = data[4]
617 | self.cmd2 = data[5]
618 | self.cmd3 = data[6]
619 | self.cmd = (self.cmd1 << 16) + (self.cmd2 << 8) + self.cmd3
620 | self.pulsehigh = data[7]
621 | self.pulselow = data[8]
622 | self.pulse = (self.pulsehigh << 8) + self.pulselow
623 | self.rssi_byte = data[9]
624 | self.rssi = self.rssi_byte >> 4
625 | self._set_strings()
626 |
627 | def set_transmit(self, subtype, seqnbr, cmd, pulse):
628 | """Load data from individual data fields"""
629 | self.packetlength = 0x09
630 | self.packettype = 0x13
631 | self.subtype = subtype
632 | self.seqnbr = seqnbr
633 | self.cmd = cmd
634 | self.cmd1 = self.cmd >> 16
635 | self.cmd2 = (self.cmd >> 8) & 0xff
636 | self.cmd3 = self.cmd & 0xff
637 | self.pulse = pulse
638 | self.pulsehigh = self.pulse >> 8
639 | self.pulselow = self.pulse & 0xff
640 | self.rssi_byte = 0
641 | self.rssi = 0
642 | self.data = bytearray([self.packetlength, self.packettype,
643 | self.subtype, self.seqnbr,
644 | self.cmd1, self.cmd2, self.cmd3,
645 | self.pulsehigh, self.pulselow, self.rssi_byte])
646 | self._set_strings()
647 |
648 | def _set_strings(self):
649 | """Translate loaded numeric values into convenience strings"""
650 | self.id_string = "{0:06x}".format(self.cmd)
651 | if self.subtype in self.TYPES:
652 | self.type_string = self.TYPES[self.subtype]
653 | else:
654 | #Degrade nicely for yet unknown subtypes
655 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
656 | self.subtype)
657 |
658 |
659 | ###############################################################################
660 | # Lighting5 class
661 | ###############################################################################
662 |
663 | class Lighting5(Packet):
664 | """
665 | Data class for the Lighting5 packet type
666 | """
667 |
668 | TYPES = {0x00: 'LightwaveRF, Siemens',
669 | 0x01: 'EMW100 GAO/Everflourish',
670 | 0x02: 'BBSB new types',
671 | 0x03: 'MDREMOTE LED dimmer',
672 | 0x04: 'Conrad RSL2',
673 | }
674 | """
675 | Mapping of numeric subtype values to strings, used in type_string
676 | """
677 |
678 | ALIAS_TYPES = {'LightwaveRF': 0x00,
679 | 'Siemens': 0x00,
680 | 'EMW100 GAO': 0x01,
681 | 'Everflourish': 0x01,
682 | }
683 | """
684 | Mapping of subtype aliases to the corresponding subtype value
685 | """
686 |
687 | COMMANDS_00 = {0x00: 'Off',
688 | 0x01: 'On',
689 | 0x02: 'Group off',
690 | 0x03: 'Mood1',
691 | 0x04: 'Mood2',
692 | 0x05: 'Mood3',
693 | 0x06: 'Mood4',
694 | 0x07: 'Mood5',
695 | 0x0a: 'Unlock',
696 | 0x0b: 'Lock',
697 | 0x0c: 'All lock',
698 | 0x0d: 'Close (inline relay)',
699 | 0x0e: 'Stop (inline relay)',
700 | 0x0f: 'Open (inline relay)',
701 | 0x10: 'Set level',
702 | }
703 | """
704 | Mapping of command numeric values to strings, used for cmnd_string
705 | """
706 |
707 | COMMANDS_01 = {0x00: 'Off',
708 | 0x01: 'On',
709 | 0x02: 'Learn',
710 | }
711 | """
712 | Mapping of command numeric values to strings, used for cmnd_string
713 | """
714 |
715 | COMMANDS_02_04 = {0x00: 'Off',
716 | 0x01: 'On',
717 | 0x02: 'Group off',
718 | 0x03: 'Group on',
719 | }
720 | """
721 | Mapping of command numeric values to strings, used for cmnd_string
722 | """
723 |
724 | COMMANDS_03 = {0x00: 'Power',
725 | 0x01: 'Light',
726 | 0x02: 'Bright',
727 | 0x03: 'Dim',
728 | 0x04: '100%',
729 | 0x05: '50%',
730 | 0x06: '25%',
731 | 0x07: 'Mode+',
732 | 0x08: 'Speed-',
733 | 0x09: 'Speed+',
734 | 0x0a: 'Mode-',
735 | }
736 | """
737 | Mapping of command numeric values to strings, used for cmnd_string
738 | """
739 |
740 | COMMANDS_XX = {0x00: 'Off',
741 | 0x01: 'On',
742 | }
743 | """
744 | Mapping of command numeric values to strings, used for cmnd_string
745 | """
746 |
747 | def __str__(self):
748 | return ("Lighting5 [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
749 | "level={4}, rssi={5}]") \
750 | .format(self.type_string, self.seqnbr, self.id_string,
751 | self.cmnd_string, self.level, self.rssi)
752 |
753 | def __init__(self):
754 | """Constructor"""
755 | super(Lighting5, self).__init__()
756 | self.id1 = None
757 | self.id2 = None
758 | self.id3 = None
759 | self.id_combined = None
760 | self.unitcode = None
761 | self.cmnd = None
762 | self.level = None
763 | self.cmnd_string = None
764 |
765 | def parse_id(self, subtype, id_string):
766 | """Parse a string id into individual components"""
767 | try:
768 | self.packettype = 0x14
769 | self.subtype = subtype
770 | self.id_combined = int(id_string[:6], 16)
771 | self.id1 = self.id_combined >> 16
772 | self.id2 = self.id_combined >> 8 & 0xff
773 | self.id3 = self.id_combined & 0xff
774 | self.unitcode = int(id_string[7:])
775 | self._set_strings()
776 | except:
777 | raise ValueError("Invalid id_string")
778 | if self.id_string != id_string:
779 | raise ValueError("Invalid id_string")
780 |
781 | def load_receive(self, data):
782 | """Load data from a bytearray"""
783 | self.data = data
784 | self.packetlength = data[0]
785 | self.packettype = data[1]
786 | self.subtype = data[2]
787 | self.seqnbr = data[3]
788 | self.id1 = data[4]
789 | self.id2 = data[5]
790 | self.id3 = data[6]
791 | self.id_combined = (self.id1 << 16) + (self.id2 << 8) + self.id3
792 | self.unitcode = data[7]
793 | self.cmnd = data[8]
794 | self.level = data[9]
795 | self.rssi_byte = data[10]
796 | self.rssi = self.rssi_byte >> 4
797 | self._set_strings()
798 |
799 | def set_transmit(self, subtype, seqnbr, id_combined, unitcode, cmnd,
800 | level):
801 | """Load data from individual data fields"""
802 | self.packetlength = 0x0a
803 | self.packettype = 0x14
804 | self.subtype = subtype
805 | self.seqnbr = seqnbr
806 | self.id_combined = id_combined
807 | self.id1 = id_combined >> 16
808 | self.id2 = id_combined >> 8 & 0xff
809 | self.id3 = id_combined & 0xff
810 | self.unitcode = unitcode
811 | self.cmnd = cmnd
812 | self.level = level
813 | self.rssi_byte = 0
814 | self.rssi = 0
815 | self.data = bytearray([self.packetlength, self.packettype,
816 | self.subtype, self.seqnbr, self.id1, self.id2,
817 | self.id3, self.unitcode, self.cmnd,
818 | self.level, self.rssi_byte])
819 | self._set_strings()
820 |
821 | def _set_strings(self):
822 | """Translate loaded numeric values into convenience strings"""
823 | self.id_string = "{0:06x}:{1}".format(self.id_combined, self.unitcode)
824 | if self.subtype in self.TYPES:
825 | self.type_string = self.TYPES[self.subtype]
826 | else:
827 | #Degrade nicely for yet unknown subtypes
828 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
829 | self.subtype)
830 | if self.cmnd is not None:
831 | if self.subtype == 0x00 and self.cmnd in self.COMMANDS_00:
832 | self.cmnd_string = self.COMMANDS_00[self.cmnd]
833 | elif self.subtype == 0x01 and self.cmnd in self.COMMANDS_01:
834 | self.cmnd_string = self.COMMANDS_01[self.cmnd]
835 | elif self.subtype == 0x02 and self.cmnd in self.COMMANDS_02_04:
836 | self.cmnd_string = self.COMMANDS_02_04[self.cmnd]
837 | elif self.subtype == 0x03 and self.cmnd in self.COMMANDS_03:
838 | self.cmnd_string = self.COMMANDS_03[self.cmnd]
839 | elif self.subtype == 0x04 and self.cmnd in self.COMMANDS_02_04:
840 | self.cmnd_string = self.COMMANDS_02_04[self.cmnd]
841 | elif self.subtype >= 0x05 and self.cmnd in self.COMMANDS_XX:
842 | self.cmnd_string = self.COMMANDS_XX[self.cmnd]
843 | else:
844 | self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
845 |
846 |
847 | ###############################################################################
848 | # Lighting6 class
849 | ###############################################################################
850 |
851 | class Lighting6(Packet):
852 | """
853 | Data class for the Lighting6 packet type
854 | """
855 |
856 | TYPES = {0x00: 'Blyss',
857 | }
858 | """
859 | Mapping of numeric subtype values to strings, used in type_string
860 | """
861 |
862 | COMMANDS = {0x00: 'On',
863 | 0x01: 'Off',
864 | 0x02: 'Group on',
865 | 0x03: 'Group off',
866 | }
867 | """
868 | Mapping of command numeric values to strings, used for cmnd_string
869 | """
870 |
871 | def __str__(self):
872 | return ("Lighting6 [subtype={0}, seqnbr={1}, id={2}, cmnd={3}, " +
873 | "cmndseqnbr={4}, rssi={5}]") \
874 | .format(self.type_string, self.seqnbr, self.id_string,
875 | self.cmnd_string, self.cmndseqnbr, self.rssi)
876 |
877 | def __init__(self):
878 | """Constructor"""
879 | super(Lighting6, self).__init__()
880 | self.id1 = None
881 | self.id2 = None
882 | self.id_combined = None
883 | self.groupcode = None
884 | self.unitcode = None
885 | self.cmnd = None
886 | self.cmndseqnbr = None
887 | self.rfu = None
888 | self.level = None
889 | self.cmnd_string = None
890 |
891 | def parse_id(self, subtype, id_string):
892 | """Parse a string id into individual components"""
893 | try:
894 | self.packettype = 0x15
895 | self.subtype = subtype
896 | self.id_combined = int(id_string[:4], 16)
897 | self.id1 = self.id_combined >> 8 & 0xff
898 | self.id2 = self.id_combined & 0xff
899 | self.groupcode = ord(id_string[5])
900 | self.unitcode = int(id_string[6:])
901 | self._set_strings()
902 | except:
903 | raise ValueError("Invalid id_string")
904 | if self.id_string != id_string:
905 | raise ValueError("Invalid id_string")
906 |
907 | def load_receive(self, data):
908 | """Load data from a bytearray"""
909 | self.data = data
910 | self.packetlength = data[0]
911 | self.packettype = data[1]
912 | self.subtype = data[2]
913 | self.seqnbr = data[3]
914 | self.id1 = data[4]
915 | self.id2 = data[5]
916 | self.id_combined = (self.id1 << 8) + self.id2
917 | self.groupcode = data[6]
918 | self.unitcode = data[7]
919 | self.cmnd = data[8]
920 | self.cmndseqnbr = data[9]
921 | self.rfu = data[10]
922 | self.rssi_byte = data[11]
923 | self.rssi = self.rssi_byte >> 4
924 | self._set_strings()
925 |
926 | def set_transmit(self, subtype, seqnbr, id_combined, groupcode, unitcode,
927 | cmnd, cmndseqnbr):
928 | """Load data from individual data fields"""
929 | self.packetlength = 0x0b
930 | self.packettype = 0x15
931 | self.subtype = subtype
932 | self.seqnbr = seqnbr
933 | self.id_combined = id_combined
934 | self.id1 = id_combined >> 8 & 0xff
935 | self.id2 = id_combined & 0xff
936 | self.groupcode = groupcode
937 | self.unitcode = unitcode
938 | self.cmnd = cmnd
939 | self.cmndseqnbr = cmndseqnbr
940 | self.rfu = 0
941 | self.rssi_byte = 0
942 | self.rssi = 0
943 | self.data = bytearray([self.packetlength, self.packettype,
944 | self.subtype, self.seqnbr, self.id1, self.id2,
945 | self.groupcode, self.unitcode, self.cmnd,
946 | self.cmndseqnbr, self.rfu, self.rssi_byte])
947 | self._set_strings()
948 |
949 | def _set_strings(self):
950 | """Translate loaded numeric values into convenience strings"""
951 | self.id_string = "{0:04x}:{1}{2}".format(self.id_combined,
952 | chr(self.groupcode),
953 | self.unitcode)
954 | if self.subtype in self.TYPES:
955 | self.type_string = self.TYPES[self.subtype]
956 | else:
957 | #Degrade nicely for yet unknown subtypes
958 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
959 | self.subtype)
960 | if self.cmnd is not None:
961 | if self.cmnd in self.COMMANDS:
962 | self.cmnd_string = self.COMMANDS[self.cmnd]
963 | else:
964 | self.cmnd_string = self._UNKNOWN_CMND.format(self.cmnd)
965 |
966 |
967 | ###############################################################################
968 | # SensorPacket class
969 | ###############################################################################
970 |
971 | class SensorPacket(Packet):
972 | """
973 | Abstract superclass for all sensor related packets
974 | """
975 |
976 | HUMIDITY_TYPES = {0x00: 'dry',
977 | 0x01: 'comfort',
978 | 0x02: 'normal',
979 | 0x03: 'wet',
980 | -1: 'unknown humidity'}
981 | """
982 | Mapping of humidity types to string
983 | """
984 |
985 | FORECAST_TYPES = {0x00: 'no forecast available',
986 | 0x01: 'sunny',
987 | 0x02: 'partly cloudy',
988 | 0x03: 'cloudy',
989 | 0x04: 'rain',
990 | -1: 'unknown forecast'}
991 | """
992 | Mapping of forecast types to string
993 | """
994 |
995 | def __init__(self):
996 | """Constructor"""
997 | super(SensorPacket, self).__init__()
998 |
999 |
1000 | ###############################################################################
1001 | # Temp class
1002 | ###############################################################################
1003 |
1004 | class Temp(SensorPacket):
1005 | """
1006 | Data class for the Temp1 packet type
1007 | """
1008 |
1009 | TYPES = {0x01: 'THR128/138, THC138',
1010 | 0x02: 'THC238/268,THN132,THWR288,THRN122,THN122,AW129/131',
1011 | 0x03: 'THWR800',
1012 | 0x04: 'RTHN318',
1013 | 0x05: 'La Crosse TX2, TX3, TX4, TX17',
1014 | 0x06: 'TS15C',
1015 | 0x07: 'Viking 02811',
1016 | 0x08: 'La Crosse WS2300',
1017 | 0x09: 'RUBiCSON',
1018 | 0x0a: 'TFA 30.3133',
1019 | }
1020 | """
1021 | Mapping of numeric subtype values to strings, used in type_string
1022 | """
1023 |
1024 | def __str__(self):
1025 | return ("Temp [subtype={0}, seqnbr={1}, id={2}, temp={3}, " +
1026 | "battery={4}, rssi={5}]") \
1027 | .format(self.type_string, self.seqnbr, self.id_string,
1028 | self.temp, self.battery, self.rssi)
1029 |
1030 | def __init__(self):
1031 | """Constructor"""
1032 | super(Temp, self).__init__()
1033 | self.id1 = None
1034 | self.id2 = None
1035 | self.temphigh = None
1036 | self.templow = None
1037 | self.temp = None
1038 | self.battery = None
1039 |
1040 | def load_receive(self, data):
1041 | """Load data from a bytearray"""
1042 | self.data = data
1043 | self.packetlength = data[0]
1044 | self.packettype = data[1]
1045 | self.subtype = data[2]
1046 | self.seqnbr = data[3]
1047 | self.id1 = data[4]
1048 | self.id2 = data[5]
1049 | self.temphigh = data[6]
1050 | self.templow = data[7]
1051 | self.temp = float(((self.temphigh & 0x7f) << 8) + self.templow) / 10
1052 | if self.temphigh >= 0x80:
1053 | self.temp = -self.temp
1054 | self.rssi_byte = data[8]
1055 | self.battery = self.rssi_byte & 0x0f
1056 | self.rssi = self.rssi_byte >> 4
1057 | self._set_strings()
1058 |
1059 | def _set_strings(self):
1060 | """Translate loaded numeric values into convenience strings"""
1061 | self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
1062 | if self.subtype in self.TYPES:
1063 | self.type_string = self.TYPES[self.subtype]
1064 | else:
1065 | #Degrade nicely for yet unknown subtypes
1066 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
1067 | self.subtype)
1068 |
1069 |
1070 | ###############################################################################
1071 | # Humid class
1072 | ###############################################################################
1073 |
1074 | class Humid(SensorPacket):
1075 | """
1076 | Data class for the Humid packet type
1077 | """
1078 |
1079 | TYPES = {0x01: 'LaCrosse TX3',
1080 | 0x02: 'LaCrosse WS2300',
1081 | }
1082 | """
1083 | Mapping of numeric subtype values to strings, used in type_string
1084 | """
1085 |
1086 | def __str__(self):
1087 | return ("Humid [subtype={0}, seqnbr={1}, id={2}, " +
1088 | "humidity={3}, humidity_status={4}, battery={5}, rssi={6}]") \
1089 | .format(self.type_string, self.seqnbr, self.id_string,
1090 | self.humidity, self.humidity_status,
1091 | self.battery, self.rssi)
1092 |
1093 | def __init__(self):
1094 | """Constructor"""
1095 | super(Humid, self).__init__()
1096 | self.id1 = None
1097 | self.id2 = None
1098 | self.humidity = None
1099 | self.humidity_status = None
1100 | self.humidity_status_string = None
1101 | self.battery = None
1102 |
1103 | def load_receive(self, data):
1104 | """Load data from a bytearray"""
1105 | self.data = data
1106 | self.packetlength = data[0]
1107 | self.packettype = data[1]
1108 | self.subtype = data[2]
1109 | self.seqnbr = data[3]
1110 | self.id1 = data[4]
1111 | self.id2 = data[5]
1112 | self.humidity = data[6]
1113 | self.humidity_status = data[7]
1114 | self.rssi_byte = data[8]
1115 | self.battery = self.rssi_byte & 0x0f
1116 | self.rssi = self.rssi_byte >> 4
1117 | self._set_strings()
1118 |
1119 | def _set_strings(self):
1120 | """Translate loaded numeric values into convenience strings"""
1121 | self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
1122 | if self.subtype in self.TYPES:
1123 | self.type_string = self.TYPES[self.subtype]
1124 | else:
1125 | #Degrade nicely for yet unknown subtypes
1126 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
1127 | self.subtype)
1128 | if self.humidity_status in self.HUMIDITY_TYPES:
1129 | self.humidity_status_string = \
1130 | self.HUMIDITY_TYPES[self.humidity_status]
1131 | else:
1132 | self.humidity_status_string = self.HUMIDITY_TYPES[-1]
1133 |
1134 |
1135 | ###############################################################################
1136 | # TempHumid class
1137 | ###############################################################################
1138 |
1139 | class TempHumid(SensorPacket):
1140 | """
1141 | Data class for the TempHumid packet type
1142 | """
1143 |
1144 | TYPES = {0x01: 'THGN122/123, THGN132, THGR122/228/238/268',
1145 | 0x02: 'THGR810, THGN800',
1146 | 0x03: 'RTGR328',
1147 | 0x04: 'THGR328',
1148 | 0x05: 'WTGR800',
1149 | 0x06: 'THGR918/928, THGRN228, THGN500',
1150 | 0x07: 'TFA TS34C, Cresta',
1151 | 0x08: 'WT260,WT260H,WT440H,WT450,WT450H',
1152 | 0x09: 'Viking 02035,02038',
1153 | 0x0a: 'Rubicson',
1154 | }
1155 | """
1156 | Mapping of numeric subtype values to strings, used in type_string
1157 | """
1158 |
1159 | def __str__(self):
1160 | return ("TempHumid [subtype={0}, seqnbr={1}, id={2}, temp={3}, " +
1161 | "humidity={4}, humidity_status={5}, battery={6}, rssi={7}]") \
1162 | .format(self.type_string, self.seqnbr, self.id_string,
1163 | self.temp, self.humidity, self.humidity_status,
1164 | self.battery, self.rssi)
1165 |
1166 | def __init__(self):
1167 | """Constructor"""
1168 | super(TempHumid, self).__init__()
1169 | self.id1 = None
1170 | self.id2 = None
1171 | self.temphigh = None
1172 | self.templow = None
1173 | self.temp = None
1174 | self.humidity = None
1175 | self.humidity_status = None
1176 | self.humidity_status_string = None
1177 | self.battery = None
1178 |
1179 | def load_receive(self, data):
1180 | """Load data from a bytearray"""
1181 | self.data = data
1182 | self.packetlength = data[0]
1183 | self.packettype = data[1]
1184 | self.subtype = data[2]
1185 | self.seqnbr = data[3]
1186 | self.id1 = data[4]
1187 | self.id2 = data[5]
1188 | self.temphigh = data[6]
1189 | self.templow = data[7]
1190 | self.temp = float(((self.temphigh & 0x7f) << 8) + self.templow) / 10
1191 | if self.temphigh >= 0x80:
1192 | self.temp = -self.temp
1193 | self.humidity = data[8]
1194 | self.humidity_status = data[9]
1195 | self.rssi_byte = data[10]
1196 | self.battery = self.rssi_byte & 0x0f
1197 | self.rssi = self.rssi_byte >> 4
1198 | self._set_strings()
1199 |
1200 | def _set_strings(self):
1201 | """Translate loaded numeric values into convenience strings"""
1202 | self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
1203 | if self.subtype in self.TYPES:
1204 | self.type_string = self.TYPES[self.subtype]
1205 | else:
1206 | #Degrade nicely for yet unknown subtypes
1207 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
1208 | self.subtype)
1209 | if self.humidity_status in self.HUMIDITY_TYPES:
1210 | self.humidity_status_string = \
1211 | self.HUMIDITY_TYPES[self.humidity_status]
1212 | else:
1213 | self.humidity_status_string = self.HUMIDITY_TYPES[-1]
1214 |
1215 |
1216 | ###############################################################################
1217 | # Baro class
1218 | ###############################################################################
1219 |
1220 | class Baro(SensorPacket):
1221 | """
1222 | Data class for the Baro packet type
1223 | """
1224 |
1225 | TYPES = {}
1226 | """
1227 | Mapping of numeric subtype values to strings, used in type_string
1228 | """
1229 |
1230 | def __str__(self):
1231 | return ("Baro [subtype={0}, seqnbr={1}, id={2}, baro={3}, " +
1232 | "forecast={4}, battery={5}, rssi={6}]") \
1233 | .format(self.type_string, self.seqnbr, self.id_string, self.baro,
1234 | self.forecast, self.battery, self.rssi)
1235 |
1236 | def __init__(self):
1237 | """Constructor"""
1238 | super(Baro, self).__init__()
1239 | self.id1 = None
1240 | self.id2 = None
1241 | self.baro1 = None
1242 | self.baro2 = None
1243 | self.baro = None
1244 | self.forecast = None
1245 | self.forecast_string = None
1246 | self.battery = None
1247 |
1248 | def load_receive(self, data):
1249 | """Load data from a bytearray"""
1250 | self.data = data
1251 | self.packetlength = data[0]
1252 | self.packettype = data[1]
1253 | self.subtype = data[2]
1254 | self.seqnbr = data[3]
1255 | self.id1 = data[4]
1256 | self.id2 = data[5]
1257 | self.baro1 = data[6]
1258 | self.baro2 = data[7]
1259 | self.baro = (self.baro1 << 8) + self.baro2
1260 | self.forecast = data[8]
1261 | self.rssi_byte = data[9]
1262 | self.battery = self.rssi_byte & 0x0f
1263 | self.rssi = self.rssi_byte >> 4
1264 | self._set_strings()
1265 |
1266 | def _set_strings(self):
1267 | """Translate loaded numeric values into convenience strings"""
1268 | self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
1269 | if self.subtype in self.TYPES:
1270 | self.type_string = self.TYPES[self.subtype]
1271 | else:
1272 | #Degrade nicely for yet unknown subtypes
1273 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
1274 | self.subtype)
1275 | if self.forecast in self.FORECAST_TYPES:
1276 | self.forecast_string = self.FORECAST_TYPES[self.forecast]
1277 | else:
1278 | self.forecast_string = self.FORECAST_TYPES[-1]
1279 |
1280 |
1281 | ###############################################################################
1282 | # TempHumidBaro class
1283 | ###############################################################################
1284 |
1285 | class TempHumidBaro(SensorPacket):
1286 | """
1287 | Data class for the TempHumidBaro packet type
1288 | """
1289 |
1290 | TYPES = {0x01: 'BTHR918',
1291 | 0x02: 'BTHR918N, BTHR968',
1292 | }
1293 | """
1294 | Mapping of numeric subtype values to strings, used in type_string
1295 | """
1296 |
1297 | def __str__(self):
1298 | return ("TempHumidBaro [subtype={0}, seqnbr={1}, id={2}, temp={3}, " +
1299 | "humidity={4}, humidity_status={5}, baro={6}, forecast={7}, " +
1300 | "battery={8}, rssi={9}]") \
1301 | .format(self.type_string, self.seqnbr, self.id_string, self.temp,
1302 | self.humidity, self.humidity_status, self.baro,
1303 | self.forecast, self.battery, self.rssi)
1304 |
1305 | def __init__(self):
1306 | """Constructor"""
1307 | super(TempHumidBaro, self).__init__()
1308 | self.id1 = None
1309 | self.id2 = None
1310 | self.temphigh = None
1311 | self.templow = None
1312 | self.temp = None
1313 | self.humidity = None
1314 | self.humidity_status = None
1315 | self.humidity_status_string = None
1316 | self.baro1 = None
1317 | self.baro2 = None
1318 | self.baro = None
1319 | self.forecast = None
1320 | self.forecast_string = None
1321 | self.battery = None
1322 |
1323 | def load_receive(self, data):
1324 | """Load data from a bytearray"""
1325 | self.data = data
1326 | self.packetlength = data[0]
1327 | self.packettype = data[1]
1328 | self.subtype = data[2]
1329 | self.seqnbr = data[3]
1330 | self.id1 = data[4]
1331 | self.id2 = data[5]
1332 | self.temphigh = data[6]
1333 | self.templow = data[7]
1334 | self.temp = float(((self.temphigh & 0x7f) << 8) + self.templow) / 10
1335 | if self.temphigh >= 0x80:
1336 | self.temp = -self.temp
1337 | self.humidity = data[8]
1338 | self.humidity_status = data[9]
1339 | self.baro1 = data[10]
1340 | self.baro2 = data[11]
1341 | self.baro = (self.baro1 << 8) + self.baro2
1342 | self.forecast = data[12]
1343 | self.rssi_byte = data[13]
1344 | self.battery = self.rssi_byte & 0x0f
1345 | self.rssi = self.rssi_byte >> 4
1346 | self._set_strings()
1347 |
1348 | def _set_strings(self):
1349 | """Translate loaded numeric values into convenience strings"""
1350 | self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
1351 | if self.subtype in self.TYPES:
1352 | self.type_string = self.TYPES[self.subtype]
1353 | else:
1354 | #Degrade nicely for yet unknown subtypes
1355 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
1356 | self.subtype)
1357 | if self.humidity_status in self.HUMIDITY_TYPES:
1358 | self.humidity_status_string = \
1359 | self.HUMIDITY_TYPES[self.humidity_status]
1360 | else:
1361 | self.humidity_status_string = self.HUMIDITY_TYPES[-1]
1362 | if self.forecast in self.FORECAST_TYPES:
1363 | self.forecast_string = self.FORECAST_TYPES[self.forecast]
1364 | else:
1365 | self.forecast_string = self.FORECAST_TYPES[-1]
1366 |
1367 |
1368 | ###############################################################################
1369 | # Rain class
1370 | ###############################################################################
1371 |
1372 | class Rain(SensorPacket):
1373 |
1374 | TYPES = {
1375 | 0x01: "RGR126/682/918",
1376 | 0x02: "PCR800",
1377 | 0x03: "TFA",
1378 | 0x04: "UPM RG700",
1379 | 0x05: "WS2300",
1380 | 0x06: "La Crosse TX5"
1381 | }
1382 |
1383 | def __str__(self):
1384 | return ("Rain [subtype={0}, seqnbr={1}, id={2}, rainrate={3}, " +
1385 | "raintotal={4}, battery={5}, rssi={6}]") \
1386 | .format(self.type_string, self.seqnbr, self.id_string,
1387 | self.rainrate, self.raintotal, self.battery, self.rssi)
1388 |
1389 | def __init__(self):
1390 | """Constructor"""
1391 | super(Rain, self).__init__()
1392 | self.id1 = None
1393 | self.id2 = None
1394 | self.rainrate1 = None
1395 | self.rainrate2 = None
1396 | self.rainrate = None
1397 | self.raintotal1 = None
1398 | self.raintotal2 = None
1399 | self.raintotal3 = None
1400 | self.raintotal = None
1401 | self.battery = None
1402 |
1403 | def load_receive(self, data):
1404 | """Load data from a bytearray"""
1405 | self.data = data
1406 | self.packetlength = data[0]
1407 | self.packettype = data[1]
1408 | self.subtype = data[2]
1409 | self.seqnbr = data[3]
1410 | self.id1 = data[4]
1411 | self.id2 = data[5]
1412 | self.rainrate1 = data[6]
1413 | self.rainrate2 = data[7]
1414 | self.rainrate = (self.rainrate1 << 8) + self.rainrate2
1415 | if self.subtype == 2:
1416 | self.rainrate = float(self.rainrate) / 100
1417 | self.raintotal1 = data[8]
1418 | self.raintotal2 = data[9]
1419 | self.raintotal3 = data[10]
1420 | self.raintotal = float((self.raintotal1 << 16) +
1421 | (self.raintotal2 << 8) +
1422 | self.raintotal3) / 10
1423 | self.rssi_byte = data[11]
1424 | self.battery = self.rssi_byte & 0x0f
1425 | self.rssi = self.rssi_byte >> 4
1426 | self._set_strings()
1427 |
1428 | def _set_strings(self):
1429 | """Translate loaded numeric values into convenience strings"""
1430 | self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
1431 | if self.subtype in self.TYPES:
1432 | self.type_string = self.TYPES[self.subtype]
1433 | else:
1434 | #Degrade nicely for yet unknown subtypes
1435 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
1436 | self.subtype)
1437 |
1438 |
1439 |
1440 | ###############################################################################
1441 | # Wind class
1442 | ###############################################################################
1443 |
1444 | class Wind(SensorPacket):
1445 | """
1446 | Data class for the Wind packet type
1447 | """
1448 |
1449 | TYPES = {0x01: 'WTGR800',
1450 | 0x02: 'WGR800',
1451 | 0x03: 'STR918, WGR918, WGR928',
1452 | 0x04: 'TFA',
1453 | 0x05: 'UPM WDS500',
1454 | 0x06: 'WS2300',
1455 | }
1456 | """
1457 | Mapping of numeric subtype values to strings, used in type_string
1458 | """
1459 |
1460 | def __str__(self):
1461 | return ("Wind [subtype={0}, seqnbr={1}, id={2}, direction={3}, " +
1462 | "average_speed={4}, gust={5}, temperature={6}, chill={7}, " +
1463 | "battery={8}, rssi={9}]") \
1464 | .format(self.type_string, self.seqnbr, self.id_string,
1465 | self.direction, self.average_speed, self.gust,
1466 | self.temperature, self.chill, self.battery, self.rssi)
1467 |
1468 | def __init__(self):
1469 | """Constructor"""
1470 | super(Wind, self).__init__()
1471 | self.id1 = None
1472 | self.id2 = None
1473 | self.direction = None
1474 | self.average_speed = None
1475 | self.gust = None
1476 | self.temperature = None
1477 | self.chill = None
1478 | self.battery = None
1479 | self.rssi = None
1480 |
1481 | def load_receive(self, data):
1482 | """Load data from a bytearray"""
1483 | self.data = data
1484 | self.packetlength = data[0]
1485 | self.packettype = data[1]
1486 | self.subtype = data[2]
1487 | self.seqnbr = data[3]
1488 | self.id1 = data[4]
1489 | self.id2 = data[5]
1490 | self.direction = data[6] * 256 + data[7]
1491 | self.average_speed = data[8] * 256.0 + data[9] / 10.0
1492 | self.gust = data[10] * 256.0 + data[11] / 10.0
1493 | self.temperature = (-1 * (data[12] >> 7)) * (
1494 | (data[12] & 0x7f) * 256.0 + data[13]) / 10.0
1495 | self.chill = (-1 * (data[14] >> 7)) * (
1496 | (data[14] & 0x7f) * 256.0 + data[15]) / 10.0
1497 | if self.subtype == 0x03:
1498 | self.battery = data[16] + 1 * 10
1499 | else:
1500 | self.rssi_byte = data[16]
1501 | self.battery = self.rssi_byte & 0x0f
1502 | self.rssi = self.rssi_byte >> 4
1503 | self._set_strings()
1504 |
1505 | def _set_strings(self):
1506 | """Translate loaded numeric values into convenience strings"""
1507 | self.id_string = "{0:02x}:{1:02x}".format(self.id1, self.id2)
1508 | if self.subtype in self.TYPES:
1509 | self.type_string = self.TYPES[self.subtype]
1510 | else:
1511 | #Degrade nicely for yet unknown subtypes
1512 | self.type_string = self._UNKNOWN_TYPE.format(self.packettype,
1513 | self.subtype)
1514 |
--------------------------------------------------------------------------------
/RFXtrx/pyserial.py:
--------------------------------------------------------------------------------
1 | # This file is part of pyRFXtrx, a Python library to communicate with
2 | # the RFXtrx family of devices from http://www.rfxcom.com/
3 | # See https://github.com/woudt/pyRFXtrx for the latest version.
4 | #
5 | # Copyright (C) 2012 Edwin Woudt
6 | #
7 | # pyRFXtrx is free software: you can redistribute it and/or modify it
8 | # under the terms of the GNU Lesser General Public License as published
9 | # by the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # pyRFXtrx is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public License
18 | # along with pyRFXtrx. See the file COPYING.txt in the distribution.
19 | # If not, see .
20 | """
21 | This module provides a transport for PySerial
22 | """
23 |
24 | from serial import Serial
25 | from time import sleep
26 | from . import RFXtrxTransport
27 |
28 |
29 | class PySerialTransport(RFXtrxTransport):
30 | """ Implementation of a transport using PySerial """
31 |
32 | def __init__(self, port, debug=False):
33 | self.serial = Serial(port, 38400, timeout=0.1)
34 | self.debug = debug
35 |
36 | def receive_blocking(self):
37 | """ Wait until a packet is received and return with an RFXtrxEvent """
38 | while True:
39 | data = self.serial.read()
40 | if (len(data) > 0):
41 | if data == '\x00':
42 | continue
43 | pkt = bytearray(data)
44 | data = self.serial.read(pkt[0])
45 | pkt.extend(bytearray(data))
46 | if self.debug:
47 | print("Recv: " + " ".join("0x{0:02x}".format(x)
48 | for x in pkt))
49 | return self.parse(pkt)
50 |
51 | def send(self, data):
52 | """ Send the given packet """
53 | if isinstance(data, bytearray):
54 | pkt = data
55 | elif isinstance(data, str) or isinstance(data, bytes):
56 | pkt = bytearray(data)
57 | else:
58 | raise ValueError("Invalid type")
59 | if self.debug:
60 | print ("Send: " + " ".join("0x{0:02x}".format(x) for x in pkt))
61 | self.serial.write(pkt)
62 |
63 | def reset(self):
64 | """ Reset the RFXtrx """
65 | self.send('\x0D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
66 | sleep(0.3) # Should work with 0.05, but not for me
67 | self.serial.flushInput()
68 | self.send('\x0D\x00\x00\x01\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00')
69 | return self.receive_blocking()
70 |
--------------------------------------------------------------------------------
/RFXtrx/twistedserial.py:
--------------------------------------------------------------------------------
1 | # This file is part of pyRFXtrx, a Python library to communicate with
2 | # the RFXtrx family of devices from http://www.rfxcom.com/
3 | # See https://github.com/woudt/pyRFXtrx for the latest version.
4 | #
5 | # Copyright (C) 2012 Edwin Woudt
6 | #
7 | # pyRFXtrx is free software: you can redistribute it and/or modify it
8 | # under the terms of the GNU Lesser General Public License as published
9 | # by the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # pyRFXtrx is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public License
18 | # along with pyRFXtrx. See the file COPYING.txt in the distribution.
19 | # If not, see .
20 | """
21 | This module provides a transport and protocol implementation for using pyRFXtrx
22 | with the Twisted framework
23 | """
24 | # pylint: disable=C0103,E0611,E1101,F0401
25 |
26 | from twisted.internet import reactor
27 | from twisted.internet.protocol import Protocol
28 | from twisted.internet.serialport import SerialPort
29 |
30 | from . import RFXtrxTransport
31 |
32 |
33 | class FixedSerialPort(SerialPort):
34 | def connectionLost(self, reason):
35 | SerialPort.connectionLost(self, reason)
36 | self.protocol.connectionLost(reason)
37 |
38 | class _TwistedSerialProtocol(Protocol):
39 | """ Twisted Protocol implementation, used internally by
40 | TwistedSerialTransport
41 | """
42 |
43 | def __init__(self, receive_callback, reset_callback, disconnected_callback):
44 | self.receive_callback = receive_callback
45 | self.reset_callback = reset_callback
46 | self.disconnected_callback = disconnected_callback
47 | self.buffer = bytearray([])
48 |
49 | def dataReceived(self, data):
50 | """ Called by Twisted when data is received """
51 | bdata = bytearray(data)
52 | self.buffer.extend(bdata)
53 | if len(self.buffer) == self.buffer[0] + 1:
54 | self.receive_callback(self.buffer)
55 | self.buffer = bytearray([])
56 |
57 | def connectionMade(self):
58 | """ Called by Twisted when the connection is made """
59 | self.reset_callback()
60 |
61 | def connectionLost(self, reason):
62 | if self.disconnected_callback:
63 | self.disconnected_callback()
64 |
65 | class TwistedSerialTransport(RFXtrxTransport):
66 | """ Transport implementation for the Twisted framework """
67 |
68 | def __init__(self, port, receive_callback, disconnected_callback=None, debug=False):
69 | self.debug = debug
70 | self.receive_callback = receive_callback
71 | self.protocol = _TwistedSerialProtocol(self._receive,
72 | self._reset,
73 | disconnected_callback)
74 | self.port = port
75 | self.serial = FixedSerialPort(self.protocol, self.port, reactor)
76 | self.serial.setBaudRate(38400)
77 |
78 | def _receive(self, data):
79 | """ Handle a received packet """
80 | if self.debug:
81 | print("Recv: " + " ".join("0x{0:02x}".format(x) for x in data))
82 | pkt = self.parse(data)
83 | self.receive_callback(pkt)
84 |
85 | def send(self, data):
86 | """ Send the given packet """
87 | if self.debug:
88 | bdata = bytearray(data)
89 | print ("Send: " + " ".join("0x{0:02x}".format(x) for x in bdata))
90 | self.protocol.transport.write(str(data))
91 |
92 | def _reset(self):
93 | """ Reset the RFXtrx """
94 | self.send('\x0D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
95 | reactor.callLater(0.3, self._get_status)
96 |
97 | def _get_status(self):
98 | """ Get the status of the RFXtrx after a reset """
99 | self.send('\x0D\x00\x00\x01\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00')
100 |
--------------------------------------------------------------------------------
/doctest/all_versions.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | python2.6 --version
4 | python2.6 -m doctest -v doctest/lighting.txt | grep failed
5 | python2.6 -m doctest -v doctest/lowlevel.txt | grep failed
6 |
7 | python2.7 --version
8 | python2.7 -m doctest -v doctest/lighting.txt | grep failed
9 | python2.7 -m doctest -v doctest/lowlevel.txt | grep failed
10 |
11 | python3.1 --version
12 | python3.1 -m doctest -v doctest/lighting.txt | grep failed
13 | python3.1 -m doctest -v doctest/lowlevel.txt | grep failed
14 |
15 | python3.2 --version
16 | python3.2 -m doctest -v doctest/lighting.txt | grep failed
17 | python3.2 -m doctest -v doctest/lowlevel.txt | grep failed
18 |
19 | python3.3 --version
20 | python3.3 -m doctest -v doctest/lighting.txt | grep failed
21 | python3.3 -m doctest -v doctest/lowlevel.txt | grep failed
22 |
--------------------------------------------------------------------------------
/doctest/lighting.txt:
--------------------------------------------------------------------------------
1 | Lighting tests
2 | ==============
3 |
4 |
5 | Lighting1
6 | ---------
7 |
8 | >>> from RFXtrx import dummy, get_device
9 | >>> x = get_device(0x10, 0x00, 'E13')
10 | >>> print(x)
11 | type='X10 lighting' id='E13'
12 | >>> transport = dummy.DummyTransport()
13 | >>> x = transport.receive([0x07, 0x10, 0x00, 0x2a, 0x45, 0x05, 0x01, 0x70])
14 | Recv: 0x07 0x10 0x00 0x2a 0x45 0x05 0x01 0x70
15 | >>> print(x)
16 | device=[ type='X10 lighting' id='E5'] values=[('Command', 'On'), ('Rssi numeric', 7)]
17 | >>> x.device.send_on(transport)
18 | Send: 0x07 0x10 0x00 0x00 0x45 0x05 0x01 0x00
19 | >>> x.device.send_off(transport)
20 | Send: 0x07 0x10 0x00 0x00 0x45 0x05 0x00 0x00
21 |
22 |
23 | Lighting2
24 | ---------
25 |
26 | >>> from RFXtrx import dummy, get_device
27 | >>> x = get_device(0x11, 0x00, '1234567:5')
28 | >>> print(x)
29 | type='AC' id='1234567:5'
30 | >>> transport = dummy.DummyTransport()
31 | >>> x = transport.receive([0x0b, 0x11, 0x00, 0x2a, 0x01, 0x23, 0x45, 0x67, 0x05, 0x02, 0x07, 0x70])
32 | Recv: 0x0b 0x11 0x00 0x2a 0x01 0x23 0x45 0x67 0x05 0x02 0x07 0x70
33 | >>> print(x)
34 | device=[ type='AC' id='1234567:5'] values=[('Command', 'Set level'), ('Dim level', 50), ('Rssi numeric', 7)]
35 | >>> x.device.send_on(transport)
36 | Send: 0x0b 0x11 0x00 0x00 0x01 0x23 0x45 0x67 0x05 0x01 0x00 0x00
37 | >>> x.device.send_off(transport)
38 | Send: 0x0b 0x11 0x00 0x00 0x01 0x23 0x45 0x67 0x05 0x00 0x00 0x00
39 | >>> x.device.send_dim(transport, 0)
40 | Send: 0x0b 0x11 0x00 0x00 0x01 0x23 0x45 0x67 0x05 0x00 0x00 0x00
41 | >>> x.device.send_dim(transport, 1)
42 | Send: 0x0b 0x11 0x00 0x00 0x01 0x23 0x45 0x67 0x05 0x02 0x00 0x00
43 | >>> x.device.send_dim(transport, 50)
44 | Send: 0x0b 0x11 0x00 0x00 0x01 0x23 0x45 0x67 0x05 0x02 0x07 0x00
45 | >>> x.device.send_dim(transport, 99)
46 | Send: 0x0b 0x11 0x00 0x00 0x01 0x23 0x45 0x67 0x05 0x02 0x0f 0x00
47 | >>> x.device.send_dim(transport, 100)
48 | Send: 0x0b 0x11 0x00 0x00 0x01 0x23 0x45 0x67 0x05 0x02 0x0f 0x00
49 |
50 |
51 | Lighting3
52 | ---------
53 |
54 | >>> from RFXtrx import dummy, get_device
55 | >>> x = get_device(0x12, 0x00, '1:234')
56 | >>> print(x)
57 | type='Ikea Koppla' id='1:234'
58 | >>> transport = dummy.DummyTransport()
59 | >>> x = transport.receive([0x08, 0x12, 0x00, 0x2a, 0x01, 0x34, 0x02, 0x15, 0x79])
60 | Recv: 0x08 0x12 0x00 0x2a 0x01 0x34 0x02 0x15 0x79
61 | >>> print(x)
62 | device=[ type='Ikea Koppla' id='1:234'] values=[('Command', 'Level 5'), ('Rssi numeric', 7)]
63 | >>> x.device.send_on(transport)
64 | Send: 0x08 0x12 0x00 0x00 0x01 0x34 0x02 0x10 0x00
65 | >>> x.device.send_off(transport)
66 | Send: 0x08 0x12 0x00 0x00 0x01 0x34 0x02 0x1a 0x00
67 |
68 |
69 | Lighting5
70 | ---------
71 |
72 | >>> from RFXtrx import dummy, get_device
73 | >>> x = get_device(0x14, 0x00, '123456:7')
74 | >>> print(x)
75 | type='LightwaveRF, Siemens' id='123456:7'
76 | >>> transport = dummy.DummyTransport()
77 | >>> x = transport.receive([0x0a, 0x14, 0x00, 0x2a, 0x12, 0x34, 0x56, 0x07, 0x10, 0x0f, 0x70])
78 | Recv: 0x0a 0x14 0x00 0x2a 0x12 0x34 0x56 0x07 0x10 0x0f 0x70
79 | >>> print(x)
80 | device=[ type='LightwaveRF, Siemens' id='123456:7'] values=[('Dim level', 50), ('Rssi numeric', 7)]
81 | >>> x.device.send_on(transport)
82 | Send: 0x0a 0x14 0x00 0x00 0x12 0x34 0x56 0x07 0x01 0x00 0x00
83 | >>> x.device.send_off(transport)
84 | Send: 0x0a 0x14 0x00 0x00 0x12 0x34 0x56 0x07 0x00 0x00 0x00
85 | >>> x.device.send_dim(transport, 0)
86 | Send: 0x0a 0x14 0x00 0x00 0x12 0x34 0x56 0x07 0x00 0x00 0x00
87 | >>> x.device.send_dim(transport, 1)
88 | Send: 0x0a 0x14 0x00 0x00 0x12 0x34 0x56 0x07 0x10 0x00 0x00
89 | >>> x.device.send_dim(transport, 50)
90 | Send: 0x0a 0x14 0x00 0x00 0x12 0x34 0x56 0x07 0x10 0x0f 0x00
91 | >>> x.device.send_dim(transport, 99)
92 | Send: 0x0a 0x14 0x00 0x00 0x12 0x34 0x56 0x07 0x10 0x1f 0x00
93 | >>> x.device.send_dim(transport, 100)
94 | Send: 0x0a 0x14 0x00 0x00 0x12 0x34 0x56 0x07 0x10 0x1f 0x00
95 |
96 |
97 | Lighting6
98 | ---------
99 |
100 | >>> from RFXtrx import dummy, get_device
101 | >>> x = get_device(0x15, 0x00, '1234:A5')
102 | >>> print(x)
103 | type='Blyss' id='1234:A5'
104 | >>> transport = dummy.DummyTransport()
105 | >>> x = transport.receive([0x0b, 0x15, 0x00, 0x2a, 0x12, 0x34, 0x41, 0x05, 0x03, 0x01, 0x00, 0x70])
106 | Recv: 0x0b 0x15 0x00 0x2a 0x12 0x34 0x41 0x05 0x03 0x01 0x00 0x70
107 | >>> print(x)
108 | device=[ type='Blyss' id='1234:A5'] values=[('Rssi numeric', 7)]
109 | >>> x.device.send_on(transport)
110 | Send: 0x0b 0x15 0x00 0x00 0x12 0x34 0x41 0x05 0x00 0x00 0x00 0x00
111 | >>> x.device.send_off(transport)
112 | Send: 0x0b 0x15 0x00 0x00 0x12 0x34 0x41 0x05 0x01 0x01 0x00 0x00
113 | >>> x.device.send_on(transport)
114 | Send: 0x0b 0x15 0x00 0x00 0x12 0x34 0x41 0x05 0x00 0x02 0x00 0x00
115 | >>> x.device.send_off(transport)
116 | Send: 0x0b 0x15 0x00 0x00 0x12 0x34 0x41 0x05 0x01 0x03 0x00 0x00
117 | >>> x.device.send_on(transport)
118 | Send: 0x0b 0x15 0x00 0x00 0x12 0x34 0x41 0x05 0x00 0x04 0x00 0x00
119 | >>> x.device.send_off(transport)
120 | Send: 0x0b 0x15 0x00 0x00 0x12 0x34 0x41 0x05 0x01 0x00 0x00 0x00
121 |
--------------------------------------------------------------------------------
/doctest/lowlevel.txt:
--------------------------------------------------------------------------------
1 | Doctests for the lowlevel module
2 | ================================
3 |
4 | This file is part of pyRFXtrx, a Python library to communicate with
5 | the RFXtrx family of devices from http://www.rfxcom.com/
6 | See https://github.com/woudt/pyRFXtrx for the latest version.
7 |
8 | Copyright (C) 2012 Edwin Woudt
9 |
10 | pyRFXtrx is free software: you can redistribute it and/or modify it
11 | under the terms of the GNU Lesser General Public License as published
12 | by the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | pyRFXtrx is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU Lesser General Public License for more details.
19 |
20 | You should have received a copy of the GNU Lesser General Public License
21 | along with pyRFXtrx. See the file COPYING.txt in the distribution.
22 | If not, see .
23 |
24 | Status
25 | ------
26 |
27 | >>> from RFXtrx import lowlevel
28 | >>>
29 | >>> x = lowlevel.Status()
30 | >>> print(x)
31 | Status [subtype=None, firmware=None, devices=None]
32 | >>> x.load_receive(bytearray([0x0d, 0x01, 0x00, 0x01, 0x02, 0x53, 0x3e, 0x00, 0x0c, 0x2f, 0x01, 0x01, 0x00, 0x00]))
33 | >>> print(x)
34 | Status [subtype=433.92MHz, firmware=62, devices=['ac', 'arc', 'hideki', 'homeeasy', 'lacrosse', 'oregon', 'x10']]
35 | >>>
36 | >>> print(list(x.data))
37 | [13, 1, 0, 1, 2, 83, 62, 0, 12, 47, 1, 1, 0, 0]
38 | >>> print(x.packetlength)
39 | 13
40 | >>> print(x.packettype)
41 | 1
42 | >>> print(x.tranceiver_type)
43 | 83
44 | >>> print(x.firmware_version)
45 | 62
46 | >>> print(x.type_string)
47 | 433.92MHz
48 | >>> print(x.devices)
49 | ['ac', 'arc', 'hideki', 'homeeasy', 'lacrosse', 'oregon', 'x10']
50 |
51 |
52 | Lighting1
53 | ---------
54 |
55 | >>> from RFXtrx import lowlevel
56 | >>>
57 | >>> x = lowlevel.Lighting1()
58 | >>> print(x)
59 | Lighting1 [subtype=None, seqnbr=None, id=None, cmnd=None, rssi=None]
60 | >>> x.load_receive(bytearray([0x07, 0x10, 0x00, 0x2a, 0x45, 0x05, 0x01, 0x70]))
61 | >>> print(x)
62 | Lighting1 [subtype=X10 lighting, seqnbr=42, id=E5, cmnd=On, rssi=7]
63 | >>>
64 | >>> print(list(x.data))
65 | [7, 16, 0, 42, 69, 5, 1, 112]
66 | >>> print(x.packetlength)
67 | 7
68 | >>> print(x.packettype)
69 | 16
70 | >>> print(x.subtype)
71 | 0
72 | >>> print(x.type_string)
73 | X10 lighting
74 | >>> print(x.seqnbr)
75 | 42
76 | >>> print(x.housecode)
77 | 69
78 | >>> print(x.unitcode)
79 | 5
80 | >>> print(x.id_string)
81 | E5
82 | >>> print(x.cmnd)
83 | 1
84 | >>> print(x.cmnd_string)
85 | On
86 | >>> print(x.rssi_byte)
87 | 112
88 | >>> print(x.rssi)
89 | 7
90 | >>>
91 | >>> x = lowlevel.Lighting1()
92 | >>> x.set_transmit(0x00, 0x2a, 0x45, 0x05, 0x01)
93 | >>> print(x)
94 | Lighting1 [subtype=X10 lighting, seqnbr=42, id=E5, cmnd=On, rssi=0]
95 | >>>
96 | >>> print(list(x.data))
97 | [7, 16, 0, 42, 69, 5, 1, 0]
98 | >>> print(x.packetlength)
99 | 7
100 | >>> print(x.packettype)
101 | 16
102 | >>> print(x.subtype)
103 | 0
104 | >>> print(x.type_string)
105 | X10 lighting
106 | >>> print(x.seqnbr)
107 | 42
108 | >>> print(x.housecode)
109 | 69
110 | >>> print(x.unitcode)
111 | 5
112 | >>> print(x.id_string)
113 | E5
114 | >>> print(x.cmnd)
115 | 1
116 | >>> print(x.cmnd_string)
117 | On
118 | >>> print(x.rssi_byte)
119 | 0
120 | >>> print(x.rssi)
121 | 0
122 | >>>
123 | >>> x = lowlevel.Lighting1()
124 | >>> x.parse_id(0, "E13")
125 | >>> print(x)
126 | Lighting1 [subtype=X10 lighting, seqnbr=None, id=E13, cmnd=None, rssi=None]
127 | >>> print(x.housecode)
128 | 69
129 | >>> print(x.unitcode)
130 | 13
131 | >>> x.parse_id(0, "Q1")
132 | Traceback (most recent call last):
133 | File "", line 1, in
134 | File "RFXtrx/lowlevel.py", line 280, in parse_id
135 | raise ValueError("Invalid id_string")
136 | ValueError: Invalid id_string
137 | >>> x.parse_id(0, "AA")
138 | Traceback (most recent call last):
139 | File "", line 1, in
140 | File "RFXtrx/lowlevel.py", line 280, in parse_id
141 | raise ValueError("Invalid id_string")
142 | ValueError: Invalid id_string
143 |
144 |
145 | Lighting2
146 | ---------
147 |
148 | >>> from RFXtrx import lowlevel
149 | >>>
150 | >>> x = lowlevel.Lighting2()
151 | >>> print(x)
152 | Lighting2 [subtype=None, seqnbr=None, id=None, cmnd=None, level=None, rssi=None]
153 | >>> x.load_receive(bytearray([0x0b, 0x11, 0x00, 0x2a, 0x01, 0x23, 0x45, 0x67, 0x05, 0x02, 0x08, 0x70]))
154 | >>> print(x)
155 | Lighting2 [subtype=AC, seqnbr=42, id=1234567:5, cmnd=Set level, level=8, rssi=7]
156 | >>>
157 | >>> print(list(x.data))
158 | [11, 17, 0, 42, 1, 35, 69, 103, 5, 2, 8, 112]
159 | >>> print(x.packetlength)
160 | 11
161 | >>> print(x.packettype)
162 | 17
163 | >>> print(x.subtype)
164 | 0
165 | >>> print(x.type_string)
166 | AC
167 | >>> print(x.seqnbr)
168 | 42
169 | >>> print(x.id1)
170 | 1
171 | >>> print(x.id2)
172 | 35
173 | >>> print(x.id3)
174 | 69
175 | >>> print(x.id4)
176 | 103
177 | >>> print(x.id_combined)
178 | 19088743
179 | >>> print(x.unitcode)
180 | 5
181 | >>> print(x.id_string)
182 | 1234567:5
183 | >>> print(x.cmnd)
184 | 2
185 | >>> print(x.cmnd_string)
186 | Set level
187 | >>> print(x.level)
188 | 8
189 | >>> print(x.rssi_byte)
190 | 112
191 | >>> print(x.rssi)
192 | 7
193 | >>>
194 | >>> x = lowlevel.Lighting2()
195 | >>> x.set_transmit(0x00, 0x2a, 0x1234567, 0x05, 0x02, 0x08)
196 | >>> print(x)
197 | Lighting2 [subtype=AC, seqnbr=42, id=1234567:5, cmnd=Set level, level=8, rssi=0]
198 | >>>
199 | >>> print(list(x.data))
200 | [11, 17, 0, 42, 1, 35, 69, 103, 5, 2, 8, 0]
201 | >>> print(x.packetlength)
202 | 11
203 | >>> print(x.packettype)
204 | 17
205 | >>> print(x.subtype)
206 | 0
207 | >>> print(x.type_string)
208 | AC
209 | >>> print(x.seqnbr)
210 | 42
211 | >>> print(x.id1)
212 | 1
213 | >>> print(x.id2)
214 | 35
215 | >>> print(x.id3)
216 | 69
217 | >>> print(x.id4)
218 | 103
219 | >>> print(x.id_combined)
220 | 19088743
221 | >>> print(x.unitcode)
222 | 5
223 | >>> print(x.id_string)
224 | 1234567:5
225 | >>> print(x.cmnd)
226 | 2
227 | >>> print(x.cmnd_string)
228 | Set level
229 | >>> print(x.level)
230 | 8
231 | >>> print(x.rssi_byte)
232 | 0
233 | >>> print(x.rssi)
234 | 0
235 | >>>
236 | >>> x = lowlevel.Lighting2()
237 | >>> x.parse_id(0, "1234567:5")
238 | >>> print(x)
239 | Lighting2 [subtype=AC, seqnbr=None, id=1234567:5, cmnd=None, level=None, rssi=None]
240 | >>> print(x.id1)
241 | 1
242 | >>> print(x.id2)
243 | 35
244 | >>> print(x.id3)
245 | 69
246 | >>> print(x.id4)
247 | 103
248 | >>> print(x.id_combined)
249 | 19088743
250 | >>> print(x.unitcode)
251 | 5
252 | >>> x.parse_id(0, "12345678:5")
253 | Traceback (most recent call last):
254 | File "", line 1, in
255 | File "RFXtrx/lowlevel.py", line 280, in parse_id
256 | raise ValueError("Invalid id_string")
257 | ValueError: Invalid id_string
258 | >>> x.parse_id(0, "123456:54")
259 | Traceback (most recent call last):
260 | File "", line 1, in
261 | File "RFXtrx/lowlevel.py", line 280, in parse_id
262 | raise ValueError("Invalid id_string")
263 | ValueError: Invalid id_string
264 | >>> x.parse_id(0, "123456785")
265 | Traceback (most recent call last):
266 | File "", line 1, in
267 | File "RFXtrx/lowlevel.py", line 280, in parse_id
268 | raise ValueError("Invalid id_string")
269 | ValueError: Invalid id_string
270 |
271 |
272 | Lighting3
273 | ---------
274 |
275 | >>> from RFXtrx import lowlevel
276 | >>>
277 | >>> x = lowlevel.Lighting3()
278 | >>> print(x)
279 | Lighting3 [subtype=None, seqnbr=None, id=None, cmnd=None, battery=None, rssi=None]
280 | >>> x.load_receive(bytearray([0x08, 0x12, 0x00, 0x2a, 0x01, 0x34, 0x02, 0x15, 0x79]))
281 | >>> print(x)
282 | Lighting3 [subtype=Ikea Koppla, seqnbr=42, id=1:234, cmnd=Level 5, battery=9, rssi=7]
283 | >>>
284 | >>> print(list(x.data))
285 | [8, 18, 0, 42, 1, 52, 2, 21, 121]
286 | >>> print(x.packetlength)
287 | 8
288 | >>> print(x.packettype)
289 | 18
290 | >>> print(x.subtype)
291 | 0
292 | >>> print(x.type_string)
293 | Ikea Koppla
294 | >>> print(x.seqnbr)
295 | 42
296 | >>> print(x.system)
297 | 1
298 | >>> print(x.channel1)
299 | 52
300 | >>> print(x.channel2)
301 | 2
302 | >>> print(x.channel)
303 | 564
304 | >>> print(x.id_string)
305 | 1:234
306 | >>> print(x.cmnd)
307 | 21
308 | >>> print(x.cmnd_string)
309 | Level 5
310 | >>> print(x.rssi_byte)
311 | 121
312 | >>> print(x.rssi)
313 | 7
314 | >>> print(x.battery)
315 | 9
316 | >>>
317 | >>> x = lowlevel.Lighting3()
318 | >>> x.set_transmit(0x00, 0x2a, 0x1, 0x234, 0x15)
319 | >>> print(x)
320 | Lighting3 [subtype=Ikea Koppla, seqnbr=42, id=1:234, cmnd=Level 5, battery=0, rssi=0]
321 | >>>
322 | >>> print(list(x.data))
323 | [8, 18, 0, 42, 1, 52, 2, 21, 0]
324 | >>> print(x.packetlength)
325 | 8
326 | >>> print(x.packettype)
327 | 18
328 | >>> print(x.subtype)
329 | 0
330 | >>> print(x.type_string)
331 | Ikea Koppla
332 | >>> print(x.seqnbr)
333 | 42
334 | >>> print(x.system)
335 | 1
336 | >>> print(x.channel1)
337 | 52
338 | >>> print(x.channel2)
339 | 2
340 | >>> print(x.channel)
341 | 564
342 | >>> print(x.id_string)
343 | 1:234
344 | >>> print(x.cmnd)
345 | 21
346 | >>> print(x.cmnd_string)
347 | Level 5
348 | >>> print(x.rssi_byte)
349 | 0
350 | >>> print(x.rssi)
351 | 0
352 | >>> print(x.battery)
353 | 0
354 | >>> x = lowlevel.Lighting3()
355 | >>> x.parse_id(0, "1:234")
356 | >>> print(x)
357 | Lighting3 [subtype=Ikea Koppla, seqnbr=None, id=1:234, cmnd=None, battery=None, rssi=None]
358 | >>> print(x.system)
359 | 1
360 | >>> print(x.channel1)
361 | 52
362 | >>> print(x.channel2)
363 | 2
364 | >>> print(x.channel)
365 | 564
366 | >>> x.parse_id(0, "G:234")
367 | Traceback (most recent call last):
368 | File "", line 1, in
369 | File "RFXtrx/lowlevel.py", line 280, in parse_id
370 | raise ValueError("Invalid id_string")
371 | ValueError: Invalid id_string
372 | >>> x.parse_id(0, "10234")
373 | Traceback (most recent call last):
374 | File "", line 1, in
375 | File "RFXtrx/lowlevel.py", line 280, in parse_id
376 | raise ValueError("Invalid id_string")
377 | ValueError: Invalid id_string
378 | >>> x.parse_id(0, "1:23X")
379 | Traceback (most recent call last):
380 | File "", line 1, in
381 | File "RFXtrx/lowlevel.py", line 280, in parse_id
382 | raise ValueError("Invalid id_string")
383 | ValueError: Invalid id_string
384 |
385 |
386 | Lighting4
387 | ---------
388 |
389 | >>> from RFXtrx import lowlevel
390 | >>>
391 | >>> x = lowlevel.Lighting4()
392 | >>> print(x)
393 | Lighting4 [subtype=None, seqnbr=None, cmd=None, pulse=None, rssi=None]
394 | >>> x.load_receive(bytearray([0x09, 0x13, 0x00, 0x2a, 0x12, 0x34, 0x56, 0x01, 0x5e, 0x70]))
395 | >>> print(x)
396 | Lighting4 [subtype=PT2262, seqnbr=42, cmd=123456, pulse=350, rssi=7]
397 | >>>
398 | >>> print(list(x.data))
399 | [9, 19, 0, 42, 18, 52, 86, 1, 94, 112]
400 | >>>
401 | >>> print(x.packetlength)
402 | 9
403 | >>> print(x.packettype)
404 | 19
405 | >>> print(x.subtype)
406 | 0
407 | >>> print(x.type_string)
408 | PT2262
409 | >>> print(x.seqnbr)
410 | 42
411 | >>> print(x.cmd1)
412 | 18
413 | >>> print(x.cmd2)
414 | 52
415 | >>> print(x.cmd3)
416 | 86
417 | >>> print(x.cmd)
418 | 1193046
419 | >>> print(x.id_string)
420 | 123456
421 | >>> print(x.pulsehigh)
422 | 1
423 | >>> print(x.pulselow)
424 | 94
425 | >>> print(x.pulse)
426 | 350
427 | >>> print(x.rssi_byte)
428 | 112
429 | >>> print(x.rssi)
430 | 7
431 | >>>
432 | >>> x = lowlevel.Lighting4()
433 | >>> x.set_transmit(0x00, 0x2a, 0x123456, 0x15e)
434 | >>> print(x)
435 | Lighting4 [subtype=PT2262, seqnbr=42, cmd=123456, pulse=350, rssi=0]
436 | >>>
437 | >>> print(list(x.data))
438 | [9, 19, 0, 42, 18, 52, 86, 1, 94, 0]
439 | >>>
440 | >>> print(x.packetlength)
441 | 9
442 | >>> print(x.packettype)
443 | 19
444 | >>> print(x.subtype)
445 | 0
446 | >>> print(x.type_string)
447 | PT2262
448 | >>> print(x.seqnbr)
449 | 42
450 | >>> print(x.cmd1)
451 | 18
452 | >>> print(x.cmd2)
453 | 52
454 | >>> print(x.cmd3)
455 | 86
456 | >>> print(x.cmd)
457 | 1193046
458 | >>> print(x.id_string)
459 | 123456
460 | >>> print(x.pulsehigh)
461 | 1
462 | >>> print(x.pulselow)
463 | 94
464 | >>> print(x.pulse)
465 | 350
466 | >>> print(x.rssi_byte)
467 | 0
468 | >>> print(x.rssi)
469 | 0
470 | >>> x = lowlevel.Lighting4()
471 | >>> x.parse_id(0, "123456")
472 | >>> print(x)
473 | Lighting4 [subtype=PT2262, seqnbr=None, cmd=123456, pulse=None, rssi=None]
474 | >>> print(x.cmd1)
475 | 18
476 | >>> print(x.cmd2)
477 | 52
478 | >>> print(x.cmd3)
479 | 86
480 | >>> print(x.cmd)
481 | 1193046
482 | >>> x.parse_id(0, "12345X")
483 | Traceback (most recent call last):
484 | File "", line 1, in
485 | File "RFXtrx/lowlevel.py", line 280, in parse_id
486 | raise ValueError("Invalid id_string")
487 | ValueError: Invalid id_string
488 |
489 |
490 | Lighting5
491 | ---------
492 |
493 | >>> from RFXtrx import lowlevel
494 | >>>
495 | >>> x = lowlevel.Lighting5()
496 | >>> print(x)
497 | Lighting5 [subtype=None, seqnbr=None, id=None, cmnd=None, level=None, rssi=None]
498 | >>> x.load_receive(bytearray([0x0a, 0x14, 0x00, 0x2a, 0x12, 0x34, 0x56, 0x07, 0x10, 0x11, 0x70]))
499 | >>> print(x)
500 | Lighting5 [subtype=LightwaveRF, Siemens, seqnbr=42, id=123456:7, cmnd=Set level, level=17, rssi=7]
501 | >>>
502 | >>> print(list(x.data))
503 | [10, 20, 0, 42, 18, 52, 86, 7, 16, 17, 112]
504 | >>> print(x.packetlength)
505 | 10
506 | >>> print(x.packettype)
507 | 20
508 | >>> print(x.subtype)
509 | 0
510 | >>> print(x.type_string)
511 | LightwaveRF, Siemens
512 | >>> print(x.seqnbr)
513 | 42
514 | >>> print(x.id1)
515 | 18
516 | >>> print(x.id2)
517 | 52
518 | >>> print(x.id3)
519 | 86
520 | >>> print(x.id_combined)
521 | 1193046
522 | >>> print(x.unitcode)
523 | 7
524 | >>> print(x.id_string)
525 | 123456:7
526 | >>> print(x.cmnd)
527 | 16
528 | >>> print(x.cmnd_string)
529 | Set level
530 | >>> print(x.level)
531 | 17
532 | >>> print(x.rssi_byte)
533 | 112
534 | >>> print(x.rssi)
535 | 7
536 | >>>
537 | >>> x = lowlevel.Lighting5()
538 | >>> x.set_transmit(0x00, 0x2a, 0x123456, 0x07, 0x10, 0x11)
539 | >>> print(x)
540 | Lighting5 [subtype=LightwaveRF, Siemens, seqnbr=42, id=123456:7, cmnd=Set level, level=17, rssi=0]
541 | >>>
542 | >>> print(list(x.data))
543 | [10, 20, 0, 42, 18, 52, 86, 7, 16, 17, 0]
544 | >>> print(x.packetlength)
545 | 10
546 | >>> print(x.packettype)
547 | 20
548 | >>> print(x.subtype)
549 | 0
550 | >>> print(x.type_string)
551 | LightwaveRF, Siemens
552 | >>> print(x.seqnbr)
553 | 42
554 | >>> print(x.id1)
555 | 18
556 | >>> print(x.id2)
557 | 52
558 | >>> print(x.id3)
559 | 86
560 | >>> print(x.id_combined)
561 | 1193046
562 | >>> print(x.unitcode)
563 | 7
564 | >>> print(x.id_string)
565 | 123456:7
566 | >>> print(x.cmnd)
567 | 16
568 | >>> print(x.cmnd_string)
569 | Set level
570 | >>> print(x.level)
571 | 17
572 | >>> print(x.rssi_byte)
573 | 0
574 | >>> print(x.rssi)
575 | 0
576 | >>> x = lowlevel.Lighting5()
577 | >>> x.parse_id(0, "123456:7")
578 | >>> print(x)
579 | Lighting5 [subtype=LightwaveRF, Siemens, seqnbr=None, id=123456:7, cmnd=None, level=None, rssi=None]
580 | >>> print(x.id1)
581 | 18
582 | >>> print(x.id2)
583 | 52
584 | >>> print(x.id3)
585 | 86
586 | >>> print(x.id_combined)
587 | 1193046
588 | >>> print(x.unitcode)
589 | 7
590 | >>> x.parse_id(0, "123456:X")
591 | Traceback (most recent call last):
592 | File "", line 1, in
593 | File "RFXtrx/lowlevel.py", line 280, in parse_id
594 | raise ValueError("Invalid id_string")
595 | ValueError: Invalid id_string
596 | >>> x.parse_id(0, "12345X:7")
597 | Traceback (most recent call last):
598 | File "", line 1, in
599 | File "RFXtrx/lowlevel.py", line 280, in parse_id
600 | raise ValueError("Invalid id_string")
601 | ValueError: Invalid id_string
602 | >>> x.parse_id(0, "12345677")
603 | Traceback (most recent call last):
604 | File "", line 1, in
605 | File "RFXtrx/lowlevel.py", line 280, in parse_id
606 | raise ValueError("Invalid id_string")
607 | ValueError: Invalid id_string
608 | >>> x.parse_id(0, "1234567:8")
609 | Traceback (most recent call last):
610 | File "", line 1, in
611 | File "RFXtrx/lowlevel.py", line 280, in parse_id
612 | raise ValueError("Invalid id_string")
613 | ValueError: Invalid id_string
614 | >>> x.parse_id(0, "12345:8")
615 | Traceback (most recent call last):
616 | File "", line 1, in
617 | File "RFXtrx/lowlevel.py", line 280, in parse_id
618 | raise ValueError("Invalid id_string")
619 | ValueError: Invalid id_string
620 |
621 |
622 | Lighting6
623 | ---------
624 |
625 | >>> from RFXtrx import lowlevel
626 | >>>
627 | >>> x = lowlevel.Lighting6()
628 | >>> print(x)
629 | Lighting6 [subtype=None, seqnbr=None, id=None, cmnd=None, cmndseqnbr=None, rssi=None]
630 | >>> x.load_receive(bytearray([0x0b, 0x15, 0x00, 0x2a, 0x12, 0x34, 0x41, 0x05, 0x03, 0x01, 0x00, 0x70]))
631 | >>> print(x)
632 | Lighting6 [subtype=Blyss, seqnbr=42, id=1234:A5, cmnd=Group off, cmndseqnbr=1, rssi=7]
633 | >>>
634 | >>> print(list(x.data))
635 | [11, 21, 0, 42, 18, 52, 65, 5, 3, 1, 0, 112]
636 | >>>
637 | >>> print(x.packetlength)
638 | 11
639 | >>> print(x.packettype)
640 | 21
641 | >>> print(x.subtype)
642 | 0
643 | >>> print(x.type_string)
644 | Blyss
645 | >>> print(x.seqnbr)
646 | 42
647 | >>> print(x.id1)
648 | 18
649 | >>> print(x.id2)
650 | 52
651 | >>> print(x.id_combined)
652 | 4660
653 | >>> print(x.groupcode)
654 | 65
655 | >>> print(x.unitcode)
656 | 5
657 | >>> print(x.id_string)
658 | 1234:A5
659 | >>> print(x.cmnd)
660 | 3
661 | >>> print(x.cmnd_string)
662 | Group off
663 | >>> print(x.cmndseqnbr)
664 | 1
665 | >>> print(x.rfu)
666 | 0
667 | >>> print(x.rssi_byte)
668 | 112
669 | >>> print(x.rssi)
670 | 7
671 | >>>
672 | >>> x = lowlevel.Lighting6()
673 | >>> x.set_transmit(0x00, 0x2a, 0x1234, 0x41, 0x05, 0x03, 0x01)
674 | >>> print(x)
675 | Lighting6 [subtype=Blyss, seqnbr=42, id=1234:A5, cmnd=Group off, cmndseqnbr=1, rssi=0]
676 | >>>
677 | >>> print(list(x.data))
678 | [11, 21, 0, 42, 18, 52, 65, 5, 3, 1, 0, 0]
679 | >>>
680 | >>> print(x.packetlength)
681 | 11
682 | >>> print(x.packettype)
683 | 21
684 | >>> print(x.subtype)
685 | 0
686 | >>> print(x.type_string)
687 | Blyss
688 | >>> print(x.seqnbr)
689 | 42
690 | >>> print(x.id1)
691 | 18
692 | >>> print(x.id2)
693 | 52
694 | >>> print(x.id_combined)
695 | 4660
696 | >>> print(x.groupcode)
697 | 65
698 | >>> print(x.unitcode)
699 | 5
700 | >>> print(x.id_string)
701 | 1234:A5
702 | >>> print(x.cmnd)
703 | 3
704 | >>> print(x.cmnd_string)
705 | Group off
706 | >>> print(x.cmndseqnbr)
707 | 1
708 | >>> print(x.rfu)
709 | 0
710 | >>> print(x.rssi_byte)
711 | 0
712 | >>> print(x.rssi)
713 | 0
714 | >>> x = lowlevel.Lighting6()
715 | >>> x.parse_id(0, "1234:A5")
716 | >>> print(x)
717 | Lighting6 [subtype=Blyss, seqnbr=None, id=1234:A5, cmnd=None, cmndseqnbr=None, rssi=None]
718 | >>> print(x.id1)
719 | 18
720 | >>> print(x.id2)
721 | 52
722 | >>> print(x.id_combined)
723 | 4660
724 | >>> print(x.groupcode)
725 | 65
726 | >>> print(x.unitcode)
727 | 5
728 | >>> x.parse_id(0, "1234:AA")
729 | Traceback (most recent call last):
730 | File "", line 1, in
731 | File "RFXtrx/lowlevel.py", line 280, in parse_id
732 | raise ValueError("Invalid id_string")
733 | ValueError: Invalid id_string
734 | >>> x.parse_id(0, "123X:A5")
735 | Traceback (most recent call last):
736 | File "", line 1, in
737 | File "RFXtrx/lowlevel.py", line 280, in parse_id
738 | raise ValueError("Invalid id_string")
739 | ValueError: Invalid id_string
740 | >>> x.parse_id(0, "12345A5")
741 | Traceback (most recent call last):
742 | File "", line 1, in
743 | File "RFXtrx/lowlevel.py", line 280, in parse_id
744 | raise ValueError("Invalid id_string")
745 | ValueError: Invalid id_string
746 | >>> x.parse_id(0, "12345:A5")
747 | Traceback (most recent call last):
748 | File "", line 1, in
749 | File "RFXtrx/lowlevel.py", line 280, in parse_id
750 | raise ValueError("Invalid id_string")
751 | ValueError: Invalid id_string
752 | >>> x.parse_id(0, "123:A5")
753 | Traceback (most recent call last):
754 | File "", line 1, in
755 | File "RFXtrx/lowlevel.py", line 280, in parse_id
756 | raise ValueError("Invalid id_string")
757 | ValueError: Invalid id_string
758 |
759 |
760 | Curtain1
761 | --------
762 |
763 | Blinds1
764 | -------
765 |
766 | Security1
767 | ---------
768 |
769 | Camera1
770 | -------
771 |
772 | Remote1
773 | -------
774 |
775 | Thermostat1
776 | -----------
777 |
778 | Thermostat2
779 | -----------
780 |
781 | Thermostat3
782 | -----------
783 |
784 | Temp
785 | ----
786 |
787 | >>> from RFXtrx import lowlevel
788 | >>>
789 | >>> x = lowlevel.Temp()
790 | >>> print(x)
791 | Temp [subtype=None, seqnbr=None, id=None, temp=None, battery=None, rssi=None]
792 | >>> x.load_receive(bytearray([0x08, 0x50, 0x02, 0x2a, 0x96, 0x03, 0x81, 0x41, 0x79]))
793 | >>> print(x)
794 | Temp [subtype=THC238/268,THN132,THWR288,THRN122,THN122,AW129/131, seqnbr=42, id=96:03, temp=-32.1, battery=9, rssi=7]
795 | >>>
796 | >>> print(list(x.data))
797 | [8, 80, 2, 42, 150, 3, 129, 65, 121]
798 | >>> print(x.packetlength)
799 | 8
800 | >>> print(x.packettype)
801 | 80
802 | >>> print(x.subtype)
803 | 2
804 | >>> print(x.type_string)
805 | THC238/268,THN132,THWR288,THRN122,THN122,AW129/131
806 | >>> print(x.seqnbr)
807 | 42
808 | >>> print(x.id1)
809 | 150
810 | >>> print(x.id2)
811 | 3
812 | >>> print(x.id_string)
813 | 96:03
814 | >>> print(x.temphigh)
815 | 129
816 | >>> print(x.templow)
817 | 65
818 | >>> print(x.temp)
819 | -32.1
820 | >>> print(x.rssi_byte)
821 | 121
822 | >>> print(x.rssi)
823 | 7
824 | >>> print(x.battery)
825 | 9
826 |
827 |
828 | Humid
829 | -----
830 |
831 | >>> from RFXtrx import lowlevel
832 | >>>
833 | >>> x = lowlevel.Humid()
834 | >>> print(x)
835 | Humid [subtype=None, seqnbr=None, id=None, humidity=None, humidity_status=None, battery=None, rssi=None]
836 | >>> x.load_receive(bytearray([0x08, 0x51, 0x01, 0x2a, 0x96, 0x03, 0x60, 0x03, 0x79]))
837 | >>> print(x)
838 | Humid [subtype=LaCrosse TX3, seqnbr=42, id=96:03, humidity=96, humidity_status=3, battery=9, rssi=7]
839 | >>>
840 | >>> print(list(x.data))
841 | [8, 81, 1, 42, 150, 3, 96, 3, 121]
842 | >>> print(x.packetlength)
843 | 8
844 | >>> print(x.packettype)
845 | 81
846 | >>> print(x.subtype)
847 | 1
848 | >>> print(x.type_string)
849 | LaCrosse TX3
850 | >>> print(x.seqnbr)
851 | 42
852 | >>> print(x.id1)
853 | 150
854 | >>> print(x.id2)
855 | 3
856 | >>> print(x.id_string)
857 | 96:03
858 | >>> print(x.humidity)
859 | 96
860 | >>> print(x.humidity_status)
861 | 3
862 | >>> print(x.rssi_byte)
863 | 121
864 | >>> print(x.rssi)
865 | 7
866 | >>> print(x.battery)
867 | 9
868 |
869 |
870 | TempHumid
871 | ---------
872 |
873 | >>> from RFXtrx import lowlevel
874 | >>>
875 | >>> x = lowlevel.TempHumid()
876 | >>> print(x)
877 | TempHumid [subtype=None, seqnbr=None, id=None, temp=None, humidity=None, humidity_status=None, battery=None, rssi=None]
878 | >>> x.load_receive(bytearray([0x0a, 0x52, 0x01, 0x2a, 0x96, 0x03, 0x81, 0x41, 0x60, 0x03, 0x79]))
879 | >>> print(x)
880 | TempHumid [subtype=THGN122/123, THGN132, THGR122/228/238/268, seqnbr=42, id=96:03, temp=-32.1, humidity=96, humidity_status=3, battery=9, rssi=7]
881 | >>>
882 | >>> print(list(x.data))
883 | [10, 82, 1, 42, 150, 3, 129, 65, 96, 3, 121]
884 | >>> print(x.packetlength)
885 | 10
886 | >>> print(x.packettype)
887 | 82
888 | >>> print(x.subtype)
889 | 1
890 | >>> print(x.type_string)
891 | THGN122/123, THGN132, THGR122/228/238/268
892 | >>> print(x.seqnbr)
893 | 42
894 | >>> print(x.id1)
895 | 150
896 | >>> print(x.id2)
897 | 3
898 | >>> print(x.id_string)
899 | 96:03
900 | >>> print(x.temphigh)
901 | 129
902 | >>> print(x.templow)
903 | 65
904 | >>> print(x.temp)
905 | -32.1
906 | >>> print(x.humidity)
907 | 96
908 | >>> print(x.humidity_status)
909 | 3
910 | >>> print(x.rssi_byte)
911 | 121
912 | >>> print(x.rssi)
913 | 7
914 | >>> print(x.battery)
915 | 9
916 |
917 |
918 | Baro
919 | ----
920 |
921 | >>> from RFXtrx import lowlevel
922 | >>>
923 | >>> x = lowlevel.Baro()
924 | >>> print(x)
925 | Baro [subtype=None, seqnbr=None, id=None, baro=None, forecast=None, battery=None, rssi=None]
926 | >>> x.load_receive(bytearray([0x09, 0x53, 0x01, 0x2a, 0x96, 0x03, 0x04, 0x06, 0x00, 0x79]))
927 | >>> print(x)
928 | Baro [subtype=Unknown type (0x53/0x01), seqnbr=42, id=96:03, baro=1030, forecast=0, battery=9, rssi=7]
929 | >>>
930 | >>> print(list(x.data))
931 | [9, 83, 1, 42, 150, 3, 4, 6, 0, 121]
932 | >>> print(x.packetlength)
933 | 9
934 | >>> print(x.packettype)
935 | 83
936 | >>> print(x.subtype)
937 | 1
938 | >>> print(x.type_string)
939 | Unknown type (0x53/0x01)
940 | >>> print(x.seqnbr)
941 | 42
942 | >>> print(x.id1)
943 | 150
944 | >>> print(x.id2)
945 | 3
946 | >>> print(x.id_string)
947 | 96:03
948 | >>> print(x.baro1)
949 | 4
950 | >>> print(x.baro2)
951 | 6
952 | >>> print(x.baro)
953 | 1030
954 | >>> print(x.forecast)
955 | 0
956 | >>> print(x.rssi_byte)
957 | 121
958 | >>> print(x.rssi)
959 | 7
960 | >>> print(x.battery)
961 | 9
962 |
963 |
964 | TempHumidBaro
965 | -------------
966 |
967 | >>> from RFXtrx import lowlevel
968 | >>>
969 | >>> x = lowlevel.TempHumidBaro()
970 | >>> print(x)
971 | TempHumidBaro [subtype=None, seqnbr=None, id=None, temp=None, humidity=None, humidity_status=None, baro=None, forecast=None, battery=None, rssi=None]
972 | >>> x.load_receive(bytearray([0x0d, 0x54, 0x01, 0x2a, 0x96, 0x03, 0x81, 0x41, 0x60, 0x03, 0x04, 0x06, 0x00, 0x79]))
973 | >>> print(x)
974 | TempHumidBaro [subtype=BTHR918, seqnbr=42, id=96:03, temp=-32.1, humidity=96, humidity_status=3, baro=1030, forecast=0, battery=9, rssi=7]
975 | >>>
976 | >>> print(list(x.data))
977 | [13, 84, 1, 42, 150, 3, 129, 65, 96, 3, 4, 6, 0, 121]
978 | >>> print(x.packetlength)
979 | 13
980 | >>> print(x.packettype)
981 | 84
982 | >>> print(x.subtype)
983 | 1
984 | >>> print(x.type_string)
985 | BTHR918
986 | >>> print(x.seqnbr)
987 | 42
988 | >>> print(x.id1)
989 | 150
990 | >>> print(x.id2)
991 | 3
992 | >>> print(x.id_string)
993 | 96:03
994 | >>> print(x.temphigh)
995 | 129
996 | >>> print(x.templow)
997 | 65
998 | >>> print(x.temp)
999 | -32.1
1000 | >>> print(x.humidity)
1001 | 96
1002 | >>> print(x.humidity_status)
1003 | 3
1004 | >>> print(x.baro1)
1005 | 4
1006 | >>> print(x.baro2)
1007 | 6
1008 | >>> print(x.baro)
1009 | 1030
1010 | >>> print(x.forecast)
1011 | 0
1012 | >>> print(x.rssi_byte)
1013 | 121
1014 | >>> print(x.rssi)
1015 | 7
1016 | >>> print(x.battery)
1017 | 9
1018 |
1019 |
1020 | Rain
1021 | ----
1022 | >>> from RFXtrx import lowlevel
1023 | >>>
1024 | >>> x = lowlevel.Rain()
1025 | >>> print(x)
1026 | Rain [subtype=None, seqnbr=None, id=None, rainrate=None, raintotal=None, battery=None, rssi=None]
1027 | >>> x.load_receive(bytearray([0x0b, 0x55, 0x02, 0x03, 0x12, 0x34, 0x02, 0x50, 0x01, 0x23, 0x45, 0x57]))
1028 | >>> print(x)
1029 | Rain [subtype=PCR800, seqnbr=3, id=12:34, rainrate=5.92, raintotal=7456.5, battery=7, rssi=5]
1030 | >>>
1031 | >>> print(list(x.data))
1032 | [11, 85, 2, 3, 18, 52, 2, 80, 1, 35, 69, 87]
1033 | >>> print(x.packetlength)
1034 | 11
1035 | >>> print(x.packettype)
1036 | 85
1037 | >>> print(x.subtype)
1038 | 2
1039 | >>> print(x.type_string)
1040 | PCR800
1041 | >>> print(x.seqnbr)
1042 | 3
1043 | >>> print(x.id1)
1044 | 18
1045 | >>> print(x.id2)
1046 | 52
1047 | >>> print(x.id_string)
1048 | 12:34
1049 | >>> print(x.rainrate1)
1050 | 2
1051 | >>> print(x.rainrate2)
1052 | 80
1053 | >>> print(x.rainrate)
1054 | 5.92
1055 | >>> print(x.raintotal1)
1056 | 1
1057 | >>> print(x.raintotal2)
1058 | 35
1059 | >>> print(x.raintotal3)
1060 | 69
1061 | >>> print(x.raintotal)
1062 | 7456.5
1063 | >>> print(x.rssi_byte)
1064 | 87
1065 | >>> print(x.rssi)
1066 | 5
1067 | >>> print(x.battery)
1068 | 7
1069 |
1070 |
1071 | Wind
1072 | ----
1073 | >>> from RFXtrx import lowlevel
1074 | >>>
1075 | >>> x = lowlevel.Wind()
1076 | >>> print(x)
1077 | Wind [subtype=None, seqnbr=None, id=None, direction=None, average_speed=None, gust=None, temperature=None, chill=None, battery=None, rssi=None]
1078 | >>> x.load_receive(bytearray([0x10, 0x56, 0x01, 0x03, 0x2F, 0x00, 0x00, 0xF7, 0x00, 0x20, 0x00, 0x24, 0x81, 0x60, 0x82, 0x50, 0x59]))
1079 | >>> print(x)
1080 | Wind [subtype=WTGR800, seqnbr=3, id=2f:00, direction=247, average_speed=3.2, gust=3.6, temperature=-35.2, chill=-59.2, battery=9, rssi=5]
1081 | >>>
1082 | >>> print(list(x.data))
1083 | [16, 86, 1, 3, 47, 0, 0, 247, 0, 32, 0, 36, 129, 96, 130, 80, 89]
1084 | >>> print(x.packetlength)
1085 | 16
1086 | >>> print(x.packettype)
1087 | 86
1088 | >>> print(x.subtype)
1089 | 1
1090 | >>> print(x.type_string)
1091 | WTGR800
1092 | >>> print(x.seqnbr)
1093 | 3
1094 | >>> print(x.id1)
1095 | 47
1096 | >>> print(x.id2)
1097 | 0
1098 | >>> print(x.id_string)
1099 | 2f:00
1100 | >>> print(x.direction)
1101 | 247
1102 | >>> print(x.average_speed)
1103 | 3.2
1104 | >>> print(x.gust)
1105 | 3.6
1106 | >>> print(x.temperature)
1107 | -35.2
1108 | >>> print(x.chill)
1109 | -59.2
1110 | >>> print(x.rssi_byte)
1111 | 89
1112 | >>> print(x.rssi)
1113 | 5
1114 | >>> print(x.battery)
1115 | 9
1116 |
1117 |
1118 |
1119 | UV
1120 | --
1121 |
1122 | DateTime
1123 | --------
1124 |
1125 | Current
1126 | -------
1127 |
1128 | Energy
1129 | ------
1130 |
1131 | Weight
1132 | ------
1133 |
1134 | RFXSensor
1135 | ---------
1136 |
1137 | RFXMeter
1138 | --------
1139 |
1140 | FS20
1141 | ----
1142 |
1143 |
--------------------------------------------------------------------------------
/examples/receive.py:
--------------------------------------------------------------------------------
1 | # This file is part of pyRFXtrx, a Python library to communicate with
2 | # the RFXtrx family of devices from http://www.rfxcom.com/
3 | # See https://github.com/woudt/pyRFXtrx for the latest version.
4 | #
5 | # Copyright (C) 2012 Edwin Woudt
6 | #
7 | # pyRFXtrx is free software: you can redistribute it and/or modify it
8 | # under the terms of the GNU Lesser General Public License as published
9 | # by the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # pyRFXtrx is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public License
18 | # along with pyRFXtrx. See the file COPYING.txt in the distribution.
19 | # If not, see .
20 |
21 | from RFXtrx.pyserial import PySerialTransport
22 |
23 | transport = PySerialTransport('/dev/cu.usbserial-05VN8GHS', debug=True)
24 | transport.reset()
25 |
26 | while True:
27 | print(transport.receive_blocking())
28 |
--------------------------------------------------------------------------------
/examples/receive_twisted.py:
--------------------------------------------------------------------------------
1 | # This file is part of pyRFXtrx, a Python library to communicate with
2 | # the RFXtrx family of devices from http://www.rfxcom.com/
3 | # See https://github.com/woudt/pyRFXtrx for the latest version.
4 | #
5 | # Copyright (C) 2012 Edwin Woudt
6 | #
7 | # pyRFXtrx is free software: you can redistribute it and/or modify it
8 | # under the terms of the GNU Lesser General Public License as published
9 | # by the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # pyRFXtrx is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public License
18 | # along with pyRFXtrx. See the file COPYING.txt in the distribution.
19 | # If not, see .
20 |
21 | from twisted.internet import reactor
22 | from RFXtrx.twistedserial import TwistedSerialTransport
23 |
24 | def receive(event):
25 | print(event)
26 |
27 | transport = TwistedSerialTransport('/dev/cu.usbserial-05VN8GHS', receive, debug=True)
28 | reactor.run()
29 |
--------------------------------------------------------------------------------
/examples/send.py:
--------------------------------------------------------------------------------
1 | # This file is part of pyRFXtrx, a Python library to communicate with
2 | # the RFXtrx family of devices from http://www.rfxcom.com/
3 | # See https://github.com/woudt/pyRFXtrx for the latest version.
4 | #
5 | # Copyright (C) 2012 Edwin Woudt
6 | #
7 | # pyRFXtrx is free software: you can redistribute it and/or modify it
8 | # under the terms of the GNU Lesser General Public License as published
9 | # by the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # pyRFXtrx is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public License
18 | # along with pyRFXtrx. See the file COPYING.txt in the distribution.
19 | # If not, see .
20 |
21 | from RFXtrx.pyserial import PySerialTransport
22 | from RFXtrx import LightingDevice
23 | from time import sleep
24 |
25 | transport = PySerialTransport('/dev/cu.usbserial-05VN8GHS', debug=True)
26 | transport.reset()
27 |
28 | while True:
29 | event = transport.receive_blocking()
30 | if isinstance(event.device, LightingDevice):
31 | sleep(5)
32 | event.device.send_off(transport)
33 | ack = transport.receive_blocking()
34 |
--------------------------------------------------------------------------------
/examples/send_twisted.py:
--------------------------------------------------------------------------------
1 | # This file is part of pyRFXtrx, a Python library to communicate with
2 | # the RFXtrx family of devices from http://www.rfxcom.com/
3 | # See https://github.com/woudt/pyRFXtrx for the latest version.
4 | #
5 | # Copyright (C) 2012 Edwin Woudt
6 | #
7 | # pyRFXtrx is free software: you can redistribute it and/or modify it
8 | # under the terms of the GNU Lesser General Public License as published
9 | # by the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # pyRFXtrx is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public License
18 | # along with pyRFXtrx. See the file COPYING.txt in the distribution.
19 | # If not, see .
20 |
21 | from twisted.internet import reactor
22 | from RFXtrx.twistedserial import TwistedSerialTransport
23 | from RFXtrx import LightingDevice
24 |
25 | def receive(event):
26 | if event is not None and isinstance(event.device, LightingDevice):
27 | reactor.callLater(5, turnoff, event.device)
28 |
29 | def turnoff(device):
30 | device.send_off(transport)
31 |
32 | transport = TwistedSerialTransport('/dev/cu.usbserial-05VN8GHS', receive, debug=True)
33 | reactor.run()
34 |
--------------------------------------------------------------------------------
/lint_run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | pylint -d W0105 RFXtrx
4 |
5 | pep8 --count --statistics -v RFXtrx/
6 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | '''
2 | This file is part of pyRFXtrx, a Python library to communicate with
3 | the RFXtrx family of devices from http://www.rfxcom.com/
4 | See https://github.com/woudt/pyRFXtrx for the latest version.
5 |
6 | Copyright (C) 2012 Edwin Woudt
7 |
8 | pyRFXtrx is free software: you can redistribute it and/or modify it
9 | under the terms of the GNU Lesser General Public License as published
10 | by the Free Software Foundation, either version 3 of the License, or
11 | (at your option) any later version.
12 |
13 | pyRFXtrx is distributed in the hope that it will be useful,
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | GNU Lesser General Public License for more details.
17 |
18 | You should have received a copy of the GNU Lesser General Public License
19 | along with pyRFXtrx. See the file COPYING.txt in the distribution.
20 | If not, see .
21 | '''
22 |
23 | from setuptools import setup
24 |
25 | setup(
26 | name = 'pyRFXtrx',
27 | packages = ['RFXtrx'],
28 | version = '0.1',
29 | description = 'a library to communicate with the RFXtrx family of devices',
30 | author='Edwin Woudt',
31 | author_email='edwin@woudt.nl',
32 | url='https://github.com/woudt/pyRFXtrx',
33 | classifiers=[
34 | 'Development Status :: 3 - Alpha',
35 | 'Environment :: Other Environment',
36 | 'Intended Audience :: Developers',
37 | 'License :: OSI Approved :: ' +
38 | 'GNU Lesser General Public License v3 or later (LGPLv3+)',
39 | 'Operating System :: OS Independent',
40 | 'Programming Language :: Python',
41 | 'Programming Language :: Python :: 2.6',
42 | 'Programming Language :: Python :: 2.7',
43 | 'Programming Language :: Python :: 3',
44 | 'Programming Language :: Python :: 3.1',
45 | 'Programming Language :: Python :: 3.2',
46 | 'Programming Language :: Python :: 3.3',
47 | 'Topic :: Home Automation',
48 | 'Topic :: Software Development :: Libraries :: Python Modules'
49 | ]
50 | )
51 |
--------------------------------------------------------------------------------
/test_run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | python -m doctest -v doctest/lighting.txt
4 | python -m doctest -v doctest/lowlevel.txt
5 |
6 | # run all again without the -v verbose options, to show all errors at the end
7 | python -m doctest doctest/lighting.txt
8 | python -m doctest doctest/lowlevel.txt
9 |
--------------------------------------------------------------------------------