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