├── README.md ├── __init__.py ├── config.xml ├── doc ├── COPYING ├── readme.txt ├── rfxcmd_mysql.txt ├── rfxcmd_pgsql.txt └── rfxcmd_sqlite.txt ├── lib ├── __init__.py ├── config.py ├── rfx_command.py ├── rfx_decode.py ├── rfx_protocols.py ├── rfx_rrd.py ├── rfx_sensors.py ├── rfx_socket.py ├── rfx_utils.py ├── rfx_weewx.py └── rfx_xplcom.py ├── protocol.xml ├── rfxcmd.py ├── rfxdaemon.sh ├── rfxproto.py ├── rfxsend.py ├── trigger.xml ├── weewx.xml ├── weewx ├── rfxcmd_weewx.py └── rfxcmd_weewx_config.txt └── whitelist.xml /README.md: -------------------------------------------------------------------------------- 1 | # rfxcmd 2 | 3 | ------------------------------------------------------------------------------------------------------------------ 4 | NOTE! 5 | 6 | This repository is work in progress, an extensive rewrite of RFXcmd is ongoing. This code might not work as 7 | it should. Please check the rfxcmd_gc repository which is currently the working repo. 8 | ------------------------------------------------------------------------------------------------------------------ 9 | 10 | DESCRIPTION 11 | 12 | RFXcmd is Python script that interfaces the RFX USB devices from RFXcom http://www.rfxcom.com. 13 | 14 | All documents and other related to RFXcmd can be found at http://www.rfxcmd.eu 15 | 16 | REQUIREMENTS 17 | 18 | - Python 2.7, does not work with Python 3.x 19 | - Tested on Raspberry Pi (Debian Squeezy) with Python 2.6 20 | - Tested on Mac OSX 10.8.2 with Python 2.7.2 21 | - Tested with RFXCOM device RFXtrx433-USB (v2.1) 22 | 23 | THANKS 24 | 25 | Thanks to following users who have helped with testing, patches, ideas, bug reports, and so on (in no special order). Anders, Dimitri, Patrik, Ludwig, Jean-Michel, Jean-Baptiste, Robert, Fabien, Bert, George, Jean-Francois, Mark, Frederic, Matthew, Arno, Jean-Louis, Christophe, Fredrik, Neil, Pierre-Yves and to RFXCOM for their support. 26 | 27 | NOTES 28 | 29 | RFXCOM is a Trademark of RFSmartLink. 30 | 31 | COPYRIGHT 32 | 33 | Copyright (C) 2012-2015 Sebastian Sjoholm. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . 34 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssjoholm/rfxcmd/d07536cd060e365b1913d955933fc3e01083e1bf/__init__.py -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 1 3 | 4 | 5 | yes 6 | /dev/ttyUSB0 7 | 38400 8 | 9 9 | 10 | 11 | yes 12 | 13 | 14 | no 15 | /var/run/rfxcmd.pid 16 | 17 | 18 | no 19 | localhost 20 | rfx 21 | rfxuser 22 | rfxuser1 23 | 24 | 25 | no 26 | localhost 27 | rfx 28 | 5432 29 | rfxuser 30 | rfxuser1 31 | rfxcmd 32 | 33 | 34 | no 35 | no 36 | trigger.xml 37 | 10 38 | 39 | 40 | no 41 | sqlite.db 42 | rfxcmd 43 | 44 | 45 | error 46 | rfxcmd.log 47 | 48 | 49 | no 50 | 127.0.0.1 51 | 2003 52 | 53 | 54 | no 55 | 127.0.0.1 56 | rfxcmd- 57 | yes 58 | 59 | 60 | yes 61 | localhost 62 | 55000 63 | 64 | 65 | no 66 | whitelist.xml 67 | 68 | 69 | no 70 | weewx.xml 71 | 72 | 73 | no 74 | 75 | 76 | 77 | 0 78 | 79 | 80 | no 81 | msg.log 82 | 83 | 84 | no 85 | protocol.xml 86 | 87 | 88 | -------------------------------------------------------------------------------- /doc/COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | -------------------------------------------------------------------------------- /doc/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | RFXCMD 3 | ------ 4 | 5 | The read me is now in the wiki on GoogleCode page. 6 | 7 | http://code.google.com/p/rfxcmd/wiki/ReadMe -------------------------------------------------------------------------------- /doc/rfxcmd_mysql.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | RFXCMD_MYSQL.TXT 4 | 5 | MySQL table configuration for RFXCMD 6 | 7 | Version history 8 | 9 | R1A 29-NOV-2012 Sebastian Sjoholm 10 | * Created 11 | 12 | R1B 13-APR-2013 Sebastian Sjoholm 13 | * Added 'unixtime' field 14 | 15 | */ 16 | 17 | USE rfx; 18 | CREATE TABLE `rfxcmd` ( 19 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 20 | `datetime` datetime DEFAULT NULL, 21 | `unixtime` int(11) DEFAULT NULL, 22 | `packettype` varchar(2) DEFAULT NULL, 23 | `subtype` varchar(2) DEFAULT NULL, 24 | `seqnbr` varchar(2) DEFAULT NULL, 25 | `battery` int(2) DEFAULT NULL, 26 | `rssi` int(2) DEFAULT NULL, 27 | `processed` int(1) DEFAULT NULL, 28 | `data1` varchar(32) DEFAULT NULL, 29 | `data2` varchar(32) DEFAULT NULL, 30 | `data3` varchar(32) DEFAULT NULL, 31 | `data4` int(11) DEFAULT NULL, 32 | `data5` int(11) DEFAULT NULL, 33 | `data6` int(11) DEFAULT NULL, 34 | `data7` int(11) DEFAULT NULL, 35 | `data8` float(16,4) DEFAULT NULL, 36 | `data9` float(16,4) DEFAULT NULL, 37 | `data10` float(16,4) DEFAULT NULL, 38 | `data11` float(16,4) DEFAULT NULL, 39 | `data12` float(16,4) DEFAULT NULL, 40 | `data13` datetime DEFAULT NULL, 41 | PRIMARY KEY (`id`) 42 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -------------------------------------------------------------------------------- /doc/rfxcmd_pgsql.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | RFXCMD_PGSQL.TXT 4 | 5 | PGSAL table configuration for RFXCMD 6 | 7 | Version history 8 | 9 | R1A 12-AUG-2013 Pierre-Yves Vitre 10 | * Created 11 | 12 | R1B 04-MAY-2014 Sebastian Sjoholm 13 | * Added owner statement 14 | * Default schema to public 15 | * Set min messages to warning 16 | 17 | */ 18 | 19 | -- Set min message level 20 | set client_min_messages = warning; 21 | 22 | -- Create Table 23 | CREATE TABLE public.rfxcmd( 24 | id serial NOT NULL, 25 | datetime timestamp with time zone, 26 | unixtime integer, 27 | packettype text, 28 | subtype text, 29 | seqnbr text, 30 | battery integer, 31 | rssi integer, 32 | processed integer, 33 | data1 text, 34 | data2 text, 35 | data3 text, 36 | data4 integer, 37 | data5 integer, 38 | data6 integer, 39 | data7 integer, 40 | data8 numeric, 41 | data9 numeric, 42 | data10 numeric, 43 | data11 numeric, 44 | data12 numeric, 45 | data13 timestamp with time zone 46 | ); 47 | 48 | -- Add primary key 49 | ALTER TABLE ONLY public.rfxcmd ADD CONSTRAINT rfxcmd_pkey PRIMARY KEY (id); 50 | ALTER TABLE ONLY public.rfxcmd OWNER TO rfxuser 51 | -------------------------------------------------------------------------------- /doc/rfxcmd_sqlite.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | RFXCMD_SQLITE.TXT 4 | 5 | SQLITE table configuration for RFXCMD 6 | 7 | Version history 8 | 9 | R1A 29-NOV-2012 Sebastian Sjoholm 10 | * Created 11 | 12 | R1B 13-APR-2013 Sebastian Sjoholm 13 | * Added 'unixtime' field 14 | 15 | */ 16 | 17 | CREATE TABLE 'rfxcmd' ( 18 | 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 19 | 'datetime' TEXT, 20 | 'unixtime' INTEGER, 21 | 'packettype' TEXT, 22 | 'subtype' TEXT, 23 | 'seqnbr' TEXT, 24 | 'battery' INTEGER, 25 | 'rssi' INTEGER, 26 | 'processed' INTEGER, 27 | 'data1' TEXT, 28 | 'data2' TEXT, 29 | 'data3' TEXT, 30 | 'data4' INTEGER, 31 | 'data5' INTEGER, 32 | 'data6' INTEGER, 33 | 'data7' INTEGER, 34 | 'data8' REAL, 35 | 'data9' REAL, 36 | 'data10' REAL, 37 | 'data11' REAL, 38 | 'data12' REAL, 39 | 'data13' TEXT); -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssjoholm/rfxcmd/d07536cd060e365b1913d955933fc3e01083e1bf/lib/__init__.py -------------------------------------------------------------------------------- /lib/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=UTF-8 3 | 4 | """ 5 | 6 | CONFIG.PY 7 | 8 | Copyright (C) 2012-2013 Sebastian Sjoholm, sebastian.sjoholm@gmail.com 9 | 10 | This program is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | This program 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 General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program. If not, see . 22 | 23 | Website: http://www.rfxcmd.eu/ 24 | 25 | """ 26 | 27 | # ------------------------------------------------------------------------------ 28 | # Logging 29 | # ------------------------------------------------------------------------------ 30 | 31 | # ------------------------------------------------------------------------------ 32 | # Imports 33 | # ------------------------------------------------------------------------------ 34 | 35 | import sys 36 | import string 37 | import os 38 | 39 | # ------------------------------------------------------------------------------ 40 | def read_config(configfile, configitem): 41 | """ 42 | Read item from the configuration file 43 | 44 | :param configfile: The configuration file 45 | :type configfile: String 46 | :param configitem: The XML tag to read 47 | :type configitem: String 48 | :rtype: Returns the data for the specific XML tag 49 | :raises: Exception 50 | 51 | """ 52 | logger.debug("Open configuration file") 53 | logger.debug("File: %s", configfile) 54 | 55 | if os.path.exists(configfile): 56 | try: 57 | f = open(configfile, 'r') 58 | data = f.read() 59 | f.close() 60 | except Exception as err: 61 | logger.error("Error: %s", str(err)) 62 | raise 63 | 64 | # xml parse file data 65 | logger.debug("Parse config XML data") 66 | try: 67 | dom = minidom.parseString(data) 68 | except Exception as err: 69 | logger.error("Error: %s", str(err)) 70 | logger.debug("Error in config.xml file") 71 | print "Error: problem in the config.xml file, cannot process it" 72 | raise 73 | 74 | # Get config item 75 | logger.debug("Get the configuration item: %s", configitem) 76 | 77 | try: 78 | xmlTag = dom.getElementsByTagName( configitem )[0].toxml() 79 | logger.debug("Found: %s", xmlTag) 80 | xmlData = xmlTag.replace('<' + configitem + '>','').replace('','') 81 | logger.debug("--> %s", xmlData) 82 | except Exception as err: 83 | logger.error("Error: %s", str(err)) 84 | logger.debug("The item tag not found in the config file") 85 | xmlData = "" 86 | 87 | else: 88 | logger.error("Error: Config file does not exists.") 89 | 90 | return xmlData 91 | 92 | # ------------------------------------------------------------------------------ 93 | def read_configfile(): 94 | """ 95 | Read items from the configuration file 96 | """ 97 | if os.path.exists( cmdarg.configfile ): 98 | 99 | # ---------------------- 100 | # Serial device 101 | if (read_config(cmdarg.configfile, "serial_active") == "yes"): 102 | config.serial_active = True 103 | else: 104 | config.serial_active = False 105 | config.serial_device = read_config( cmdarg.configfile, "serial_device") 106 | config.serial_rate = read_config( cmdarg.configfile, "serial_rate") 107 | config.serial_timeout = read_config( cmdarg.configfile, "serial_timeout") 108 | 109 | logger.debug("Serial device: " + str(config.serial_device)) 110 | logger.debug("Serial rate: " + str(config.serial_rate)) 111 | logger.debug("Serial timeout: " + str(config.serial_timeout)) 112 | 113 | # ---------------------- 114 | # Process 115 | if (read_config(cmdarg.configfile, "process_rfxmsg") == "yes"): 116 | config.process_rfxmsg = True 117 | else: 118 | config.process_rfxmsg = False 119 | logger.debug("Process RFXmsg: " + str(config.process_rfxmsg)) 120 | 121 | # ---------------------- 122 | # MySQL 123 | if (read_config(cmdarg.configfile, "mysql_active") == "yes"): 124 | config.mysql_active = True 125 | else: 126 | config.mysql_active = False 127 | config.mysql_server = read_config( cmdarg.configfile, "mysql_server") 128 | config.mysql_database = read_config( cmdarg.configfile, "mysql_database") 129 | config.mysql_username = read_config( cmdarg.configfile, "mysql_username") 130 | config.mysql_password = read_config( cmdarg.configfile, "mysql_password") 131 | 132 | # ---------------------- 133 | # TRIGGER 134 | if (read_config( cmdarg.configfile, "trigger_active") == "yes"): 135 | config.trigger_active = True 136 | else: 137 | config.trigger_active = False 138 | 139 | if (read_config( cmdarg.configfile, "trigger_onematch") == "yes"): 140 | config.trigger_onematch = True 141 | else: 142 | config.trigger_onematch = False 143 | 144 | config.trigger_file = read_config( cmdarg.configfile, "trigger_file") 145 | config.trigger_timeout = read_config( cmdarg.configfile, "trigger_timeout") 146 | 147 | # ---------------------- 148 | # SQLITE 149 | if (read_config(cmdarg.configfile, "sqlite_active") == "yes"): 150 | config.sqlite_active = True 151 | else: 152 | config.sqlite_active = False 153 | config.sqlite_database = read_config(cmdarg.configfile, "sqlite_database") 154 | config.sqlite_table = read_config(cmdarg.configfile, "sqlite_table") 155 | 156 | # ---------------------- 157 | # PGSQL 158 | if (read_config(cmdarg.configfile, "pgsql_active") == "yes"): 159 | config.pgsql_active = True 160 | else: 161 | config.pgsql_active = False 162 | config.pgsql_server = read_config(cmdarg.configfile, "pgsql_server") 163 | config.pgsql_database = read_config(cmdarg.configfile, "pgsql_database") 164 | config.pgsql_port = read_config(cmdarg.configfile, "pgsql_port") 165 | config.pgsql_username = read_config(cmdarg.configfile, "pgsql_username") 166 | config.pgsql_password = read_config(cmdarg.configfile, "pgsql_password") 167 | config.pgsql_table = read_config(cmdarg.configfile, "pgsql_table") 168 | 169 | # ---------------------- 170 | # GRAPHITE 171 | if (read_config(cmdarg.configfile, "graphite_active") == "yes"): 172 | config.graphite_active = True 173 | else: 174 | config.graphite_active = False 175 | config.graphite_server = read_config(cmdarg.configfile, "graphite_server") 176 | config.graphite_port = read_config(cmdarg.configfile, "graphite_port") 177 | 178 | # ---------------------- 179 | # XPL 180 | if (read_config(cmdarg.configfile, "xpl_active") == "yes"): 181 | config.xpl_active = True 182 | config.xpl_host = read_config(cmdarg.configfile, "xpl_host") 183 | config.xpl_sourcename = read_config(cmdarg.configfile, "xpl_sourcename") 184 | if (read_config(cmdarg.configfile, "xpl_includehostname") == "yes"): 185 | config.xpl_includehostname = True 186 | else: 187 | config.xpl_includehostname = False 188 | else: 189 | config.xpl_active = False 190 | 191 | # ---------------------- 192 | # SOCKET SERVER 193 | if (read_config(cmdarg.configfile, "socketserver") == "yes"): 194 | config.socketserver = True 195 | else: 196 | config.socketserver = False 197 | config.sockethost = read_config( cmdarg.configfile, "sockethost") 198 | config.socketport = read_config( cmdarg.configfile, "socketport") 199 | logger.debug("SocketServer: " + str(config.socketserver)) 200 | logger.debug("SocketHost: " + str(config.sockethost)) 201 | logger.debug("SocketPort: " + str(config.socketport)) 202 | 203 | # ----------------------- 204 | # WHITELIST 205 | if (read_config(cmdarg.configfile, "whitelist_active") == "yes"): 206 | config.whitelist_active = True 207 | else: 208 | config.whitelist_active = False 209 | config.whitelist_file = read_config( cmdarg.configfile, "whitelist_file") 210 | logger.debug("Whitelist_active: " + str(config.whitelist_active)) 211 | logger.debug("Whitelist_file: " + str(config.whitelist_file)) 212 | 213 | # ----------------------- 214 | # DAEMON 215 | if (read_config(cmdarg.configfile, "daemon_active") == "yes"): 216 | config.daemon_active = True 217 | else: 218 | config.daemon_active = False 219 | config.daemon_pidfile = read_config( cmdarg.configfile, "daemon_pidfile") 220 | logger.debug("Daemon_active: " + str(config.daemon_active)) 221 | logger.debug("Daemon_pidfile: " + str(config.daemon_pidfile)) 222 | 223 | # ----------------------- 224 | # WEEWX 225 | if (read_config(cmdarg.configfile, "weewx_active") == "yes"): 226 | config.weewx_active = True 227 | else: 228 | config.weewx_active = False 229 | config.weewx_config = read_config( cmdarg.configfile, "weewx_config") 230 | logger.debug("WeeWx_active: " + str(config.weewx_active)) 231 | logger.debug("WeeWx_config: " + str(config.weewx_config)) 232 | 233 | # ------------------------ 234 | # RRD 235 | if (read_config(cmdarg.configfile, "rrd_active") == "yes"): 236 | config.rrd_active = True 237 | else: 238 | config.rrd_active = False 239 | 240 | # If RRD path is empty, then use the script path 241 | config.rrd_path = read_config( cmdarg.configfile, "rrd_path") 242 | if not config.rrd_path: 243 | config.rrd_path = os.path.dirname(os.path.realpath(__file__)) 244 | 245 | # ------------------------ 246 | # BAROMETRIC 247 | config.barometric = read_config(cmdarg.configfile, "barometric") 248 | 249 | # ------------------------ 250 | # LOG MESSAGES 251 | if (read_config(cmdarg.configfile, "log_msg") == "yes"): 252 | config.log_msg = True 253 | else: 254 | config.log_msg = False 255 | config.log_msgfile = read_config(cmdarg.configfile, "log_msgfile") 256 | 257 | # ------------------------ 258 | # PROTOCOLS 259 | if (read_config(cmdarg.configfile, "protocol_startup") == "yes"): 260 | config.protocol_startup = True 261 | else: 262 | config.protocol_startup = False 263 | config.protocol_file = read_config(cmdarg.configfile, "protocol_file") 264 | 265 | else: 266 | # config file not found, set default values 267 | print "Error: Configuration file not found (" + cmdarg.configfile + ")" 268 | logger.error("Error: Configuration file not found (" + cmdarg.configfile + ") Line: " + _line()) 269 | 270 | # ------------------------------------------------------------------------------ 271 | # END 272 | # ------------------------------------------------------------------------------ 273 | -------------------------------------------------------------------------------- /lib/rfx_command.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=UTF-8 3 | 4 | # ------------------------------------------------------------------------------ 5 | # 6 | # RFX_COMMAND.PY 7 | # 8 | # 2013 Sebastian Sjoholm, sebastian.sjoholm@gmail.com 9 | # 10 | # All credits for this code goes to the stackoverflow.com and posting; 11 | # http://stackoverflow.com/questions/16542422/asynchronous-subprocess-with-timeout 12 | # 13 | # Author: epicbrew 14 | # 15 | # This program is free software: you can redistribute it and/or modify 16 | # it under the terms of the GNU General Public License as published by 17 | # the Free Software Foundation, either version 3 of the License, or 18 | # (at your option) any later version. 19 | # 20 | # This program is distributed in the hope that it will be useful, 21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | # GNU General Public License for more details. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with this program. If not, see . 27 | # 28 | # Website 29 | # http://code.google.com/p/rfxcmd/ 30 | # 31 | # $Rev: 464 $ 32 | # $Date: 2013-05-01 22:41:36 +0200 (Wed, 01 May 2013) $ 33 | # 34 | # ------------------------------------------------------------------------------ 35 | 36 | # -------------------------------------------------------------------------- 37 | 38 | import logging 39 | import subprocess 40 | import threading 41 | 42 | logger = logging.getLogger('rfxcmd') 43 | 44 | class Command(object): 45 | def __init__(self, cmd): 46 | self.cmd = cmd 47 | self.process = None 48 | 49 | def run(self, timeout): 50 | def target(): 51 | logger.debug("Thread started, timeout = " + str(timeout)) 52 | self.process = subprocess.Popen(self.cmd, shell=True) 53 | self.process.communicate() 54 | logger.debug("Return code: " + str(self.process.returncode)) 55 | logger.debug("Thread finished") 56 | self.timer.cancel() 57 | 58 | def timer_callback(): 59 | logger.debug("Thread timeout, terminate it") 60 | if self.process.poll() is None: 61 | try: 62 | self.process.kill() 63 | except OSError as error: 64 | logger.error("Error: %s " % error) 65 | logger.debug("Thread terminated") 66 | else: 67 | logger.debug("Thread not alive") 68 | 69 | thread = threading.Thread(target=target) 70 | self.timer = threading.Timer(int(timeout), timer_callback) 71 | self.timer.start() 72 | thread.start() 73 | 74 | # ---------------------------------------------------------------------------- 75 | -------------------------------------------------------------------------------- /lib/rfx_decode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=UTF-8 3 | 4 | # ------------------------------------------------------------------------------ 5 | # 6 | # RFX_UTILS.PY 7 | # 8 | # Copyright (C) 2012-2013 Sebastian Sjoholm, sebastian.sjoholm@gmail.com 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program 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 General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | # 23 | # Version history can be found at 24 | # http://code.google.com/p/rfxcmd/wiki/VersionHistory 25 | # 26 | # $Rev: 464 $ 27 | # $Date: 2013-05-01 22:41:36 +0200 (Wed, 01 May 2013) $ 28 | # 29 | # ------------------------------------------------------------------------------ 30 | 31 | from rfx_utils import ByteToHex 32 | from rfx_utils import clearBit 33 | from rfx_utils import testBit 34 | 35 | # ---------------------------------------------------------------------------- 36 | 37 | def decodeTemperature(message_high, message_low): 38 | """ 39 | Decode temperature bytes. 40 | """ 41 | temp_high = ByteToHex(message_high) 42 | temp_low = ByteToHex(message_low) 43 | polarity = testBit(int(temp_high,16),7) 44 | 45 | if polarity == 128: 46 | polarity_sign = "-" 47 | else: 48 | polarity_sign = "" 49 | 50 | temp_high = clearBit(int(temp_high,16),7) 51 | temp_high = temp_high << 8 52 | temperature = ( temp_high + int(temp_low,16) ) * 0.1 53 | temperature_str = polarity_sign + str(temperature) 54 | 55 | return temperature_str 56 | 57 | # ---------------------------------------------------------------------------- 58 | 59 | def decodeSignal(message): 60 | """ 61 | Decode signal byte. 62 | """ 63 | signal = int(ByteToHex(message),16) >> 4 64 | return signal 65 | 66 | # ---------------------------------------------------------------------------- 67 | 68 | def decodeBattery(message): 69 | """ 70 | Decode battery byte. 71 | """ 72 | battery = int(ByteToHex(message),16) & 0xf 73 | return battery 74 | 75 | # ---------------------------------------------------------------------------- 76 | 77 | def decodePower(message_1, message_2, message_3): 78 | """ 79 | Decode power bytes. 80 | """ 81 | power_1 = ByteToHex(message_1) 82 | power_2 = ByteToHex(message_2) 83 | power_3 = ByteToHex(message_3) 84 | 85 | power_1 = int(power_1,16) 86 | power_1 = power_1 << 16 87 | power_2 = int(power_2,16) << 8 88 | power_3 = int(power_3,16) 89 | power = ( power_1 + power_2 + power_3) 90 | power_str = str(power) 91 | 92 | return power_str 93 | 94 | # ---------------------------------------------------------------------------- 95 | -------------------------------------------------------------------------------- /lib/rfx_protocols.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=UTF-8 3 | 4 | # ------------------------------------------------------------------------------ 5 | # 6 | # RFX_PROTOCOLS.PY 7 | # 8 | # Copyright (C) 2012-2013 Sebastian Sjoholm, sebastian.sjoholm@gmail.com 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program 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 General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | # 23 | # Version history can be found at 24 | # http://code.google.com/p/rfxcmd/wiki/VersionHistory 25 | # 26 | # $Rev: 464 $ 27 | # $Date: 2013-05-01 22:41:36 +0200 (Wed, 01 May 2013) $ 28 | # 29 | # ------------------------------------------------------------------------------ 30 | 31 | # -------------------------------------------------------------------------- 32 | 33 | import logging 34 | logger = logging.getLogger('rfxcmd') 35 | 36 | # -------------------------------------------------------------------------- 37 | 38 | import sys 39 | import xml.dom.minidom as minidom 40 | 41 | # -------------------------------------------------------------------------- 42 | 43 | def print_protocolfile(protocol_file): 44 | """ 45 | Print the contents of the protocol configuration file to stdout 46 | """ 47 | logger.debug("Open protocol file and read it, file: %s" % str(protocol_file)) 48 | try: 49 | xmldoc = minidom.parse( protocol_file ) 50 | logger.debug("XML file OK") 51 | except: 52 | print("Error in %s file" % str(protocol_file)) 53 | sys.exit(1) 54 | 55 | try: 56 | data = xmldoc.documentElement.getElementsByTagName('protocol') 57 | except Exception as err: 58 | logger.error("Error: %s" % str(err)) 59 | sys.exit(1) 60 | 61 | logger.debug("Read protocol tags in file") 62 | counter = 0 63 | 64 | print("Protocol.xml configuration") 65 | print("-----------------------------------") 66 | 67 | for protocol in data: 68 | try: 69 | id = int(protocol.getElementsByTagName('id')[0].childNodes[0].nodeValue) 70 | except Exception as err: 71 | logger.error("Error: %s" % str(err)) 72 | sys.exit(1) 73 | 74 | if id <> counter: 75 | print("Error: The id number is not in order") 76 | sys.exit(1) 77 | 78 | try: 79 | name = protocol.getElementsByTagName('name')[0].childNodes[0].nodeValue 80 | except Exception as err: 81 | logger.error("Error: %s" % str(err)) 82 | sys.exit(1) 83 | 84 | try: 85 | state = int(protocol.getElementsByTagName('state')[0].childNodes[0].nodeValue) 86 | if state == 1: 87 | state_str = "Enabled" 88 | else: 89 | state_str = "Disabled" 90 | except Exception as err: 91 | logger.error("Error: %s" % str(err)) 92 | sys.exit(1) 93 | 94 | if state <> 0 and state <> 1: 95 | print("Error: The state is either 0 or 1, in protocol '%s'" % str(name)) 96 | sys.exit(1) 97 | 98 | print("%-25s %-15s" % (str(name), str(state_str))) 99 | logger.debug("Id: %s, State: %s, Counter: %s, Name: %s " % (str(id), str(state), str(counter), str(name))) 100 | counter += 1 101 | 102 | logger.debug("Tags total: %s" % str(counter)) 103 | 104 | if counter <> 24: 105 | logger.error("Error: There is not 24 protocol tags in protocol file") 106 | print("Error: There is not 24 protocol tags in protocol file") 107 | sys.exit(1) 108 | else: 109 | logger.debug("All tags found") 110 | 111 | return 112 | 113 | def set_protocolfile(protocol_file): 114 | """ 115 | Create the data message out of the protocol configuration file and return the packet 116 | """ 117 | logger.debug("Open protocol file and read it, file: %s" % str(protocol_file)) 118 | try: 119 | xmldoc = minidom.parse( protocol_file ) 120 | logger.debug("XML file OK") 121 | except: 122 | print("Error in %s file" % str(protocol_file)) 123 | sys.exit(1) 124 | 125 | try: 126 | data = xmldoc.documentElement.getElementsByTagName('protocol') 127 | except Exception as err: 128 | logger.error("Error: %s" % str(err)) 129 | sys.exit(1) 130 | 131 | logger.debug("Read protocol tags in file") 132 | counter = 0 133 | 134 | msg = [] 135 | msg3 = [] 136 | msg4 = [] 137 | msg5 = [] 138 | 139 | for protocol in data: 140 | try: 141 | id = int(protocol.getElementsByTagName('id')[0].childNodes[0].nodeValue) 142 | except Exception as err: 143 | logger.error("Error: %s" % str(err)) 144 | sys.exit(1) 145 | 146 | if id <> counter: 147 | print("Error: The id number is not in order") 148 | sys.exit(1) 149 | 150 | try: 151 | name = protocol.getElementsByTagName('name')[0].childNodes[0].nodeValue 152 | except Exception as err: 153 | logger.error("Error: %s" % str(err)) 154 | sys.exit(1) 155 | 156 | try: 157 | state = int(protocol.getElementsByTagName('state')[0].childNodes[0].nodeValue) 158 | except Exception as err: 159 | logger.error("Error: %s" % str(err)) 160 | sys.exit(1) 161 | 162 | if state <> 0 and state <> 1: 163 | print("Error: The state is either 0 or 1, in protocol '%s'" % str(name)) 164 | sys.exit(1) 165 | 166 | logger.debug("Id: %s, State: %s, Counter: %s, Name: %s " % (str(id), str(state), str(counter), str(name))) 167 | msg.insert(id, state) 168 | counter += 1 169 | 170 | logger.debug("Tags total: %s" % str(counter)) 171 | 172 | if counter <> 24: 173 | logger.error("Error: There is not 24 protocol tags in protocol file") 174 | print("Error: There is not 24 protocol tags in protocol file") 175 | sys.exit(1) 176 | else: 177 | logger.debug("All tags found") 178 | 179 | msg3 = msg[0:8] 180 | msg4 = msg[8:16] 181 | msg5 = msg[16:24] 182 | 183 | # Complete message 184 | try: 185 | msg3_bin = str(msg[0]) + str(msg[1]) + str(msg[2]) + str(msg[3]) + str(msg[4]) + str(msg[5]) + str(msg[6]) + str(msg[7]) 186 | msg3_int = int(msg3_bin,2) 187 | msg3_hex = hex(msg3_int)[2:].zfill(2) 188 | msg4_bin = str(msg[8]) + str(msg[9]) + str(msg[10]) + str(msg[11]) + str(msg[12]) + str(msg[13]) + str(msg[14]) + str(msg[15]) 189 | msg4_int = int(msg4_bin,2) 190 | msg4_hex = hex(msg4_int)[2:].zfill(2) 191 | msg5_bin = str(msg[16]) + str(msg[17]) + str(msg[18]) + str(msg[19]) + str(msg[20]) + str(msg[21]) + str(msg[22]) + str(msg[23]) 192 | msg5_int = int(msg5_bin,2) 193 | msg5_hex = hex(msg5_int)[2:].zfill(2) 194 | except Exception as err: 195 | logger.error("Error: %s" % str(err)) 196 | sys.exit(1) 197 | 198 | logger.debug("msg3: %s / %s" % (str(msg3), msg3_hex)) 199 | logger.debug("msg4: %s / %s" % (str(msg4), msg4_hex)) 200 | logger.debug("msg5: %s / %s" % (str(msg5), msg5_hex)) 201 | 202 | command = "0D000000035300%s%s%s00000000" % (msg3_hex, msg4_hex, msg5_hex) 203 | logger.debug("Command: %s" % command.upper()) 204 | 205 | return command 206 | 207 | # ---------------------------------------------------------------------------- 208 | -------------------------------------------------------------------------------- /lib/rfx_rrd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=UTF-8 3 | 4 | # ------------------------------------------------------------------------------ 5 | # 6 | # RFX_RRD.PY 7 | # 8 | # 2013 Serge Tchesmeli, serge.tchesmeli@gmail.com 9 | # This is my modest contribution to rfxcmd :) 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program. If not, see . 23 | # 24 | # Website 25 | # http://code.google.com/p/rfxcmd/ 26 | # 27 | # $Rev: 464 $ 28 | # $Date: 2013-05-01 22:41:36 +0200 (Wed, 01 May 2013) $ 29 | # 30 | # ------------------------------------------------------------------------------ 31 | 32 | # ------------------------------------------------------------------------------ 33 | 34 | try: 35 | import rrdtool 36 | except ImportError: 37 | pass 38 | 39 | import os 40 | 41 | def rrd1Metric (sensortype, sensorid, metric1, rrd_root): 42 | 43 | DS1 = "metric1" 44 | 45 | # ensure rrd dir exist 46 | if not os.path.exists (rrd_root): 47 | os.makedirs(rrd_root) 48 | 49 | # One dirs per sensor type, one rrd per sensor 50 | # If dir doesn't exist, create it. 51 | rrd_path = rrd_root + "/"+ sensortype +"/" 52 | if not os.path.exists(rrd_path): 53 | os.makedirs(rrd_path) 54 | rrdfile = rrd_path + sensorid + ".rrd" 55 | rrdfile = str(rrdfile) 56 | 57 | # If rrdfile doesn't exist, create it 58 | if not os.path.exists(rrdfile): 59 | # Legend depends of sensor type 60 | # 5A: Energy 61 | if sensortype == '5A' : 62 | DS1 = "Watt" 63 | 64 | # Create the rrd 65 | rrdtool.create(rrdfile, '--step', '30', '--start', '0', 'DS:%s:GAUGE:120:U:U' % (DS1), 'RRA:AVERAGE:0.5:1:1051200', 'RRA:AVERAGE:0.5:10:210240') 66 | 67 | # Update the rdd with new values 68 | rrdtool.update('%s' % (rrdfile), 'N:%s' % (metric1) ) 69 | 70 | def rrd2Metrics (sensortype, sensorid, metric1, metric2, rrd_root): 71 | 72 | DS1 = "metric1" 73 | DS2 = "metric2" 74 | 75 | # ensure rrd dir exist 76 | if not os.path.exists (rrd_root): 77 | os.makedirs(rrd_root) 78 | 79 | # One dirs per sensor type, one rrd per sensor 80 | # If dir doesn't exist, create it. 81 | rrd_path = rrd_root + "/"+ sensortype +"/" 82 | if not os.path.exists(rrd_path): 83 | os.makedirs(rrd_path) 84 | rrdfile = rrd_path + sensorid + ".rrd" 85 | rrdfile = str(rrdfile) 86 | 87 | # If rrdfile doesn't exist, create it 88 | if not os.path.exists(rrdfile): 89 | # Legend depends of sensor type 90 | # 52: Temperature and humidity 91 | if sensortype == '52' : 92 | DS1 = "Temperature" 93 | DS2 = "Humidity" 94 | 95 | # Create the rrd 96 | rrdtool.create(rrdfile, '--step', '30', '--start', '0', 'DS:%s:GAUGE:120:U:U' % (DS1), 'DS:%s:GAUGE:120:U:U' % (DS2), 'RRA:AVERAGE:0.5:1:1051200', 'RRA:AVERAGE:0.5:10:210240') 97 | 98 | # Update the rdd with new values 99 | rrdtool.update('%s' % (rrdfile), 'N:%s:%s' % (metric1, metric2) ) 100 | 101 | # ------------------------------------------------------------------------------ 102 | -------------------------------------------------------------------------------- /lib/rfx_sensors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=UTF-8 3 | 4 | # ------------------------------------------------------------------------------ 5 | # 6 | # RFXCMD.PY 7 | # 8 | # Copyright (C) 2012-2013 Sebastian Sjoholm, sebastian.sjoholm@gmail.com 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program 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 General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | # 23 | # Version history can be found at 24 | # http://code.google.com/p/rfxcmd/wiki/VersionHistory 25 | # 26 | # $Rev: 464 $ 27 | # $Date: 2013-05-01 22:41:36 +0200 (Wed, 01 May 2013) $ 28 | # 29 | # NOTES 30 | # 31 | # RFXCOM is a Trademark of RFSmartLink. 32 | # 33 | # ------------------------------------------------------------------------------ 34 | # 35 | # Protocol License Agreement 36 | # 37 | # The RFXtrx protocols are owned by RFXCOM, and are protected under applicable 38 | # copyright laws. 39 | # 40 | # ============================================================================== 41 | # It is only allowed to use this protocol or any part of it for RFXCOM products 42 | # ============================================================================== 43 | # 44 | # The above Protocol License Agreement and the permission notice shall be 45 | # included in all software using the RFXtrx protocols. 46 | # 47 | # Any use in violation of the foregoing restrictions may subject the user to 48 | # criminal sanctions under applicable laws, as well as to civil liability for 49 | # the breach of the terms and conditions of this license. 50 | # 51 | # ------------------------------------------------------------------------------ 52 | 53 | class rfx_data(dict): 54 | 55 | rfx_cmnd = { 56 | "00":"Reset the receiver/transceiver. No answer is transmitted!", 57 | "01":"Not used.", 58 | "02":"Get Status, return firmware versions and configuration of the interface.", 59 | "03":"Set mode msg1-msg5, return firmware versions and configuration of the interface.", 60 | "04":"Enable all receiving modes of the receiver/transceiver.", 61 | "05":"Enable reporting of undecoded packets.", 62 | "06":"Save receiving modes of the receiver/transceiver in non-volatile memory.", 63 | "07":"Not used.", 64 | "08":"T1 - for internal use by RFXCOM", 65 | "09":"T2 - for internal use by RFXCOM" 66 | } 67 | 68 | rfx_packettype = { 69 | "00":"Interface Control", 70 | "01":"Interface Message", 71 | "02":"Receiver/Transmitter Message", 72 | "03":"Undecoded RF Message", 73 | "10":"Lighting1", 74 | "11":"Lighting2", 75 | "12":"Lighting3", 76 | "13":"Lighting4", 77 | "14":"Lighting5", 78 | "15":"Lighting6", 79 | "16":"Chime", 80 | "18":"Curtain1", 81 | "19":"Blinds1", 82 | "1A":"RTS", 83 | "20":"Security1", 84 | "28":"Camera1", 85 | "30":"Remote control and IR", 86 | "40":"Thermostat1", 87 | "41":"Thermostat2 (Receive not implemented)", 88 | "42":"Thermostat3", 89 | "50":"Temperature sensors", 90 | "51":"Humidity sensors", 91 | "52":"Temperature and humidity sensors", 92 | "53":"Barometric sensors", 93 | "54":"Temperature, humidity and barometric sensors", 94 | "55":"Rain sensors", 95 | "56":"Wind sensors", 96 | "57":"UV sensors", 97 | "58":"Date/Time sensors", 98 | "59":"Current sensors", 99 | "5A":"Energy usage sensors", 100 | "5B":"Current + Energy sensors", 101 | "5C":"Power sensors", 102 | "5D":"Weighting scale", 103 | "5E":"Gas usage sensors", 104 | "5F":"Water usage sensors", 105 | "70":"RFXSensor", 106 | "71":"RFXMeter", 107 | "72":"FS20" 108 | } 109 | 110 | rfx_subtype_01 = {"00":"Response on a mode command"} 111 | 112 | rfx_subtype_01_msg1 = {"50":"310MHz", 113 | "51":"315MHz", 114 | "52":"433.92MHz (Receiver only)", 115 | "53":"433.92MHz (Transceiver)", 116 | "55":"868.00MHz", 117 | "56":"868.00MHz FSK", 118 | "57":"868.30MHz", 119 | "58":"868.30MHz FSK", 120 | "59":"868.35MHz", 121 | "5A":"868.35MHz FSK", 122 | "5B":"868.95MHz"} 123 | 124 | rfx_subtype_01_msg3 = {"128":"Undecoded", 125 | "64":"RFU", 126 | "32":"Byron SX", 127 | "16":"RSL", 128 | "8":"Lightning4", 129 | "4":"FineOffset / Viking", 130 | "2":"Rubicson", 131 | "1":"AE Blyss"} 132 | 133 | rfx_subtype_01_msg4 = {"128":"Blinds T1/T2/T3/T4", 134 | "64":"Blinds T0", 135 | "32":"ProGuard", 136 | "16":"FS20", 137 | "8":"La Crosse", 138 | "4":"Hideki / UPM", 139 | "2":"AD Lightwave RF", 140 | "1":"Mertik"} 141 | 142 | rfx_subtype_01_msg5 = {"128":"Visonic", 143 | "64":"ATI", 144 | "32":"Oregon Scientific", 145 | "16":"Meiantech", 146 | "8":"HomeEasy EU", 147 | "4":"AC", 148 | "2":"ARC", 149 | "1":"X10"} 150 | 151 | rfx_subtype_02 = {"00":"Error, receiver did not lock", 152 | "01":"Transmitter response"} 153 | 154 | rfx_subtype_02_msg1 = {"00":"ACK, transmit OK", 155 | "01":"ACK, but transmit started after 3 seconds delay anyway with RF receive data", 156 | "02":"NAK, transmitter did not lock on the requested transmit frequency", 157 | "03":"NAK, AC address zero in id1-id4 not allowed"} 158 | 159 | rfx_subtype_03 = {"00":"AC", 160 | "01":"ARC", 161 | "02":"ATI", 162 | "03":"Hideki", 163 | "04":"LaCrosse", 164 | "05":"AD", 165 | "06":"Mertik", 166 | "07":"Oregon 1", 167 | "08":"Oregon 2", 168 | "09":"Oregon 3", 169 | "0A":"Proguard", 170 | "0B":"Visionic", 171 | "0C":"NEC", 172 | "0D":"FS20", 173 | "0E":"Reserved", 174 | "0F":"Blinds", 175 | "10":"Rubicson", 176 | "11":"AE", 177 | "12":"Fineoffset"} 178 | 179 | rfx_subtype_10 = {"00":"X10 Lightning", 180 | "01":"ARC", 181 | "02":"ELRO AB400D (Flamingo)", 182 | "03":"Waveman", 183 | "04":"Chacon EMW200", 184 | "05":"IMPULS", 185 | "06":"RisingSun", 186 | "07":"Philips SBC", 187 | "08":"Energenie ENER010", 188 | "09":"Energenie 5-gang", 189 | "0A":"COCO GDR2-2000R"} 190 | 191 | rfx_subtype_10_housecode = {"41":"A", 192 | "42":"B", 193 | "43":"C", 194 | "44":"D", 195 | "45":"E", 196 | "46":"F", 197 | "47":"G", 198 | "48":"H", 199 | "49":"I", 200 | "4A":"J", 201 | "4B":"K", 202 | "4C":"L", 203 | "4D":"M", 204 | "4E":"N", 205 | "4F":"O", 206 | "50":"P"} 207 | 208 | rfx_subtype_10_cmnd = {"00":"Off", 209 | "01":"On", 210 | "02":"Dim", 211 | "03":"Bright", 212 | "05":"All/Group Off", 213 | "06":"All/Group On", 214 | "07":"Chime", 215 | "FF":"Illegal cmnd received"} 216 | 217 | rfx_subtype_11 = {"00":"AC", 218 | "01":"HomeEasy EU", 219 | "02":"Anslut"} 220 | 221 | rfx_subtype_11_cmnd = {"00":"Off", 222 | "01":"On", 223 | "02":"Set level", 224 | "03":"Group Off", 225 | "04":"Group On", 226 | "05":"Set Group Level"} 227 | 228 | rfx_subtype_11_dimlevel = {"00":"0", 229 | "01":"6", 230 | "02":"12", 231 | "03":"18", 232 | "04":"24", 233 | "05":"30", 234 | "06":"36", 235 | "07":"42", 236 | "08":"48", 237 | "09":"54", 238 | "0A":"60", 239 | "0B":"66", 240 | "0C":"72", 241 | "0D":"78", 242 | "0E":"84", 243 | "0F":"100"} 244 | 245 | rfx_subtype_12 = {"00":"Ikea Koppla"} 246 | 247 | rfx_subtype_12_cmnd = {"00":"Bright", 248 | "08":"Dim", 249 | "10":"On", 250 | "11":"Level 1", 251 | "12":"Level 2", 252 | "13":"Level 3", 253 | "14":"Level 4", 254 | "15":"Level 5", 255 | "16":"Level 6", 256 | "17":"Level 7", 257 | "18":"Level 8", 258 | "19":"Level 9", 259 | "1A":"Off", 260 | "1C":"Program"} 261 | 262 | rfx_subtype_13 = {"00":"PT2262"} 263 | 264 | rfx_subtype_14 = {"00":"LightwaveRF, Siemens", 265 | "01":"EMW100 GAO/Everflourish", 266 | "02":"BBSB new types", 267 | "03":"MDREMOTE LED dimmer", 268 | "04":"Conrad RSL2", 269 | "05":"Livolo", 270 | "06":"RGB TRC02"} 271 | 272 | # 0x00 LightwaveRF, Siemens 273 | rfx_subtype_14_cmnd0 = {"00":"Off", 274 | "01":"On", 275 | "02":"Group off", 276 | "03":"Mood1", 277 | "04":"Mood2", 278 | "05":"Mood3", 279 | "06":"Mood4", 280 | "07":"Mood5", 281 | "08":"Reserved", 282 | "09":"Reserved", 283 | "0A":"Unlock", 284 | "0B":"Lock", 285 | "0C":"All lock", 286 | "0D":"Close (inline relay)", 287 | "0E":"Stop (inline relay)", 288 | "0F":"Open (inline relay)", 289 | "10":"Set level"} 290 | 291 | # 0x01 EMW100 GAO/Everflourish 292 | rfx_subtype_14_cmnd1 = {"00":"Off", 293 | "01":"On", 294 | "02":"Learn"} 295 | 296 | # 0x02 BBSB new types 297 | rfx_subtype_14_cmnd2 = {"00":"Off", 298 | "01":"On", 299 | "02":"Group Off", 300 | "03":"Group On"} 301 | 302 | # 0x03 MDREMOTE LED dimmer 303 | rfx_subtype_14_cmnd3 = {"00":"Power", 304 | "01":"Light", 305 | "02":"Bright", 306 | "03":"Dim", 307 | "04":"100%", 308 | "05":"50%", 309 | "06":"25%", 310 | "07":"Mode+", 311 | "08":"Speed-", 312 | "09":"Speed+", 313 | "0A":"Mode-"} 314 | 315 | # 0x04 Conrad RSL2 316 | rfx_subtype_14_cmnd4 = {"00":"Off", 317 | "01":"On", 318 | "02":"Group Off", 319 | "03":"Group On"} 320 | 321 | # 0x05 Livolo 322 | rfx_subtype_14_cmnd5 = {"00":"Group Off", 323 | "01":"On/Off dimmer or gang1", 324 | "02":"Dim+ or gang2 on/off", 325 | "03":"Dim- or gang3 on/off"} 326 | 327 | # 0x06 TRC02 328 | rfx_subtype_14_cmnd6 = {"00":"Off", 329 | "01":"On", 330 | "02":"Bright", 331 | "03":"Dim", 332 | "04":"Color+", 333 | "05":"Color-"} 334 | 335 | rfx_subtype_15 = {"00":"Blyss"} 336 | 337 | rfx_subtype_15_groupcode = {"41":"A", 338 | "42":"B", 339 | "43":"C", 340 | "44":"D", 341 | "45":"E", 342 | "46":"F", 343 | "47":"G", 344 | "48":"H"} 345 | 346 | rfx_subtype_15_cmnd = {"00":"On", 347 | "01":"Off", 348 | "02":"group On", 349 | "03":"group Off"} 350 | 351 | rfx_subtype_16 = {"00":"Byron SX"} 352 | 353 | rfx_subtype_16_sound = {"01":"Tubular 3 notes", 354 | "03":"Big Ben", 355 | "05":"Tubular 2 notes", 356 | "09":"Solo", 357 | "0D":"Tubular 3 notes", 358 | "0E":"Big Ben", 359 | "06":"Tubular 2 notes", 360 | "02":"Solo"} 361 | 362 | rfx_subtype_17 = {"00":"Siemens SF01 - LF959RA50/LF259RB50/LF959RB50"} 363 | 364 | rfx_subtype_18 = {"00":"Harrison Curtain"} 365 | 366 | rfx_subtype_19 = {"00":"BlindsT0 / Rollertrol, Hasta new", 367 | "01":"BlindsT1 / Hasta old", 368 | "02":"BlindsT2 / A-OK RF01", 369 | "03":"BlindsT3 / A-OK AC114", 370 | "04":"BlindsT4 / Raex YR1326", 371 | "05":"BlindsT5 / Media Mount", 372 | "06":"BlindsT6 / DC106/Rohrmotor24-RMF/Yooda", 373 | "07":"BlindsT7 / Forest"} 374 | 375 | rfx_subtype_1A = {"00":"RTS", 376 | "01":"RTS ext (not yet fully implemented)"} 377 | 378 | rfx_subtype_1A_cmnd = {"00":"Stop", 379 | "01":"Up", 380 | "02":"Up+Stop (Set upper limit)", 381 | "03":"Down", 382 | "04":"Down+Stop (Set lower limit)", 383 | "05":"Up+Down (Connect motor)", 384 | "07":"Program", 385 | "08":"Program > 2 seconds", 386 | "09":"Program > 7 seconds", 387 | "0A":"Stop > 2 seconds (Set position / Change direction)", 388 | "0B":"Stop > 5 seconds (Set middle position)", 389 | "0C":"Up+Down > 5 seconds (Change upper position)", 390 | "0D":"Erase this RTS remote from RFXtrx", 391 | "0E":"Erase all RTS remotes from the RFXtrx"} 392 | 393 | rfx_subtype_20 = {"00":"X10 security door/window sensor", 394 | "01":"X10 security motion sensor", 395 | "02":"X10 security remote (no alive packets)", 396 | "03":"KD101 (no alive packets)", 397 | "04":"Visonic PowerCode door/window sensor - Primary contact (with alive packets)", 398 | "05":"Visonic PowerCode motion sensor (with alive packets)", 399 | "06":"Visonic CodeSecure (no alive packets)", 400 | "07":"Visonic PowerCode door/window sensor - auxiliary contact (no alive packets)", 401 | "08":"Meiantech", 402 | "09":"SA30"} 403 | 404 | rfx_subtype_20_status = {"00":"Normal", 405 | "01":"Normal delayed", 406 | "02":"Alarm", 407 | "03":"Alarm delayed", 408 | "04":"Motion", 409 | "05":"No motion", 410 | "06":"Panic", 411 | "07":"End panic", 412 | "08":"IR", 413 | "09":"Arm away", 414 | "0A":"Arm away delayed", 415 | "0B":"Arm home", 416 | "0C":"Arm home delayed", 417 | "0D":"Disarm", 418 | "10":"Light 1 off", 419 | "11":"Light 1 on", 420 | "12":"Light 2 off", 421 | "13":"Light 2 on", 422 | "14":"Dark detected", 423 | "15":"Light detected", 424 | "16":"Batlow (SD18, CO18)", 425 | "17":"Pair (KD101)", 426 | "80":"Normal + tamper", 427 | "81":"Normal delayed + tamper", 428 | "82":"Alarm + tamper", 429 | "83":"Normal delayed + tamper", 430 | "84":"Motion + tamper", 431 | "85":"No motion + tamper"} 432 | 433 | rfx_subtype_28 = {"00":"X10 Ninja"} 434 | 435 | rfx_subtype_30 = {"00":"ATI Remote Wonder", 436 | "01":"ATI Remote Wonder Plus", 437 | "02":"Medion Remote", 438 | "03":"X10 PC Remote", 439 | "04":"ATI Remote Wonder II (receive only)"} 440 | 441 | rfx_subtype_30_atiremotewonder = {"00":"A", 442 | "01":"B", 443 | "02":"Power", 444 | "03":"TV", 445 | "04":"DVD", 446 | "05":"?", 447 | "06":"Guide", 448 | "07":"Drag", 449 | "08":"VOL+", 450 | "09":"VOL-", 451 | "0A":"MUTE", 452 | "0B":"CHAN+", 453 | "0C":"CHAN-", 454 | "0D":"1", 455 | "0E":"2", 456 | "0F":"3", 457 | "10":"4", 458 | "11":"5", 459 | "12":"6", 460 | "13":"7", 461 | "14":"8", 462 | "15":"9", 463 | "16":"txt", 464 | "17":"0", 465 | "18":"Snapshot ESQ", 466 | "19":"C", 467 | "1A":"^", 468 | "1B":"D", 469 | "1C":"TV/RADIO", 470 | "1D":"<", 471 | "1E":"OK", 472 | "1F":">", 473 | "20":"<-", 474 | "21":"E", 475 | "22":"v", 476 | "23":"F", 477 | "24":"Rewind", 478 | "25":"Play", 479 | "26":"Fast forward", 480 | "27":"Record", 481 | "28":"Stop", 482 | "29":"Pause", 483 | "2C":"TV", 484 | "2D":"VCR", 485 | "2E":"RADIO", 486 | "2F":"TV Preview", 487 | "30":"Channel list", 488 | "31":"Video Desktop", 489 | "32":"red", 490 | "33":"green", 491 | "34":"yellow", 492 | "35":"blue", 493 | "36":"rename TAB", 494 | "37":"Acquire image", 495 | "38":"edit image", 496 | "39":"Full Screen", 497 | "3A":"DVD Audio", 498 | "70":"Cursor-left", 499 | "71":"Cursor-right", 500 | "72":"Cursor-up", 501 | "73":"Cursor-down", 502 | "74":"Cursor-up-left", 503 | "75":"Cursor-up-right", 504 | "76":"Cursor-down-right", 505 | "77":"Cursor-down-left", 506 | "78":"V", 507 | "79":"V-End", 508 | "7C":"X", 509 | "7D":"X-End"} 510 | 511 | rfx_subtype_30_medion = {"00":"Mute", 512 | "01":"B", 513 | "02":"Power", 514 | "03":"TV", 515 | "04":"DVD", 516 | "05":"Photo", 517 | "06":"Music", 518 | "07":"Drag", 519 | "08":"VOL-", 520 | "09":"VOL+", 521 | "0A":"MUTE", 522 | "0B":"CHAN+", 523 | "0C":"CHAN-", 524 | "0D":"1", 525 | "0E":"2", 526 | "0F":"3", 527 | "10":"4", 528 | "11":"5", 529 | "12":"6", 530 | "13":"7", 531 | "14":"8", 532 | "15":"9", 533 | "16":"txt", 534 | "17":"0", 535 | "18":"snapshot ESQ", 536 | "19":"DVD MENU", 537 | "1A":"^", 538 | "1B":"Setup", 539 | "1C":"TV/RADIO", 540 | "1D":"<", 541 | "1E":"OK", 542 | "1F":">", 543 | "20":"<-", 544 | "21":"E", 545 | "22":"v", 546 | "23":"F", 547 | "24":"Rewind", 548 | "25":"Play", 549 | "26":"Fast forward", 550 | "27":"Record", 551 | "28":"Stop", 552 | "29":"Pause", 553 | "2C":"TV", 554 | "2D":"VCR", 555 | "2E":"RADIO", 556 | "2F":"TV Preview", 557 | "30":"Channel List", 558 | "31":"Video desktop", 559 | "32":"red", 560 | "33":"green", 561 | "34":"yellow", 562 | "35":"blue", 563 | "36":"rename TAB", 564 | "37":"Acquire image", 565 | "38":"edit image", 566 | "39":"Full screen", 567 | "3A":"DVD Audio", 568 | "70":"Cursor-left", 569 | "71":"Cursor-right", 570 | "72":"Cursor-up", 571 | "73":"Cursor-down", 572 | "74":"Cursor-up-left", 573 | "75":"Cursor-up-right", 574 | "76":"Cursor-down-right", 575 | "77":"Cursor-down-left", 576 | "78":"V", 577 | "79":"V-End", 578 | "7C":"X", 579 | "7D":"X-End"} 580 | 581 | rfx_subtype_40 = {"00":"Digimax", 582 | "01":"Digimax with short format (no set point)"} 583 | 584 | rfx_subtype_40_status = {"0":"No status available", 585 | "1":"Demand", 586 | "2":"No demand", 587 | "3":"Initializing"} 588 | 589 | rfx_subtype_40_mode = {"0":"Heating", 590 | "1":"Cooling"} 591 | 592 | rfx_subtype_41 = {"00":"HE105", 593 | "01":"RTS10, RFS10, TLX1206"} 594 | 595 | rfx_subtype_42 = {"00":"Mertik G6R-H4T1", 596 | "01":"Mertik G6R-H4TB / G6-H4T / G6R-H4T21-Z22"} 597 | 598 | rfx_subtype_42_cmd00 = {"00":"Off", 599 | "01":"On", 600 | "02":"Up", 601 | "03":"Down", 602 | "04":"Run Up", 603 | "05":"Run Down", 604 | "06":"Stop"} 605 | 606 | rfx_subtype_42_cmd01 = {"00":"Off", 607 | "01":"On", 608 | "02":"Up", 609 | "03":"Down", 610 | "04":"2nd Off", 611 | "05":"2nd On"} 612 | 613 | rfx_subtype_4E = {"00":"Maverick ET-732"} 614 | 615 | rfx_subtype_50 = {"01":"THR128/138, THC138", 616 | "02":"THC238/268,THN132,THWR288,THRN122,THN122,AW129/131", 617 | "03":"THWR800", 618 | "04":"RTHN318", 619 | "05":"La Crosse TX2, TX3, TX4, TX17", 620 | "06":"TS15C", 621 | "07":"Viking 02811", 622 | "08":"La Crosse WS2300", 623 | "09":"RUBiCSON", 624 | "0A":"TFA 30.3133"} 625 | 626 | rfx_subtype_51 = {"01":"LaCrosse TX3", 627 | "02":"LaCrosse WS2300"} 628 | 629 | rfx_subtype_51_humstatus = {"00":"Dry", 630 | "01":"Comfort", 631 | "02":"Normal", 632 | "03":"Wet"} 633 | 634 | rfx_subtype_52 = {"01":"THGN122/123, THGN132, THGR122/228/238/268", 635 | "02":"THGR810, THGN800", 636 | "03":"RTGR328", 637 | "04":"THGR328", 638 | "05":"WTGR800", 639 | "06":"THGR918, THGRN228, THGN50", 640 | "07":"TFA TS34C, Cresta", 641 | "08":"WT260,WT260H,WT440H,WT450,WT450H", 642 | "09":"Viking 02035, 02038", 643 | "0A":"Rubicson", 644 | "0B":"EW109", 645 | "0C":"Imagintronix Soil Sensor"} 646 | 647 | rfx_subtype_52_humstatus = {"00":"Dry", 648 | "01":"Comfort", 649 | "02":"Normal", 650 | "03":"Wet"} 651 | 652 | rfx_subtype_53 = {"01":"Reserved for future use"} 653 | 654 | rfx_subtype_54 = {"01":"BTHR918", 655 | "02":"BTHR918N, BTHR968"} 656 | 657 | rfx_subtype_54_humstatus = {"00":"Normal", 658 | "01":"Comfort", 659 | "02":"Dry", 660 | "03":"Wet"} 661 | 662 | rfx_subtype_54_forecast = {"00":"No forecast available", 663 | "01":"Sunny", 664 | "02":"Partly cloudy", 665 | "03":"Cloudy", 666 | "04":"Rainy"} 667 | 668 | rfx_subtype_55 = {"01":"RGR126/682/918", 669 | "02":"PCR800", 670 | "03":"TFA", 671 | "04":"UPM RG700", 672 | "05":"WS2300", 673 | "06":"La Crosse TX5"} 674 | 675 | rfx_subtype_56 = {"01":"WTGR800", 676 | "02":"WGR800", 677 | "03":"STR918, WGR918, WGR928", 678 | "04":"TFA", 679 | "05":"UPM WDS500", 680 | "06":"WS2300"} 681 | 682 | rfx_subtype_57 = {"01":"UVN128, UV138", 683 | "02":"UVN800", 684 | "03":"TFA"} 685 | 686 | rfx_subtype_58 = {"01":"RTGR328N"} 687 | 688 | rfx_subtype_59 = {"01":"CM113, Electrisave, cent-a-meter"} 689 | 690 | rfx_subtype_5A = {"01":"CM119/160", 691 | "02":"CM180"} 692 | 693 | rfx_subtype_5B = {"01":"CM180i"} 694 | 695 | rfx_subtype_5C = {"01":"Revolt"} 696 | 697 | rfx_subtype_5D = {"01":"BWR101/102", 698 | "02":"GR101"} 699 | 700 | rfx_subtype_5E = {"01":"Gas usage sensor"} 701 | 702 | rfx_subtype_5F = {"01":"Water usage sensor"} 703 | 704 | rfx_subtype_70 = {"00":"RFXSensor temperature", 705 | "01":"RFXSensor A/S", 706 | "02":"RFXSensor voltage", 707 | "03":"RFXSensor message"} 708 | 709 | rfx_subtype_70_msg03 = {"01":"Sensor addresses incremented", 710 | "02":"Battery low detected", 711 | "81":"No 1-wire device connected", 712 | "82":"1-Wire ROM CRC error", 713 | "83":"1-Wire device connected is not a DS18B20 or DS2438", 714 | "84":"No end of read signal received from 1-Wire device", 715 | "85":"1-Wire scratchpad CRC error"} 716 | 717 | rfx_subtype_71 = {"00":"Normal data packet", 718 | "01":"New interval time set", 719 | "02":"Calibrate value in in usec", 720 | "03":"New address set", 721 | "04":"Counter value reset within 5 seconds", 722 | "0B":"Counter value reset executed", 723 | "0C":"Set interval mode within 5 seconds", 724 | "0D":"Calibration mode within 5 seconds", 725 | "0E":"Set address mode within 5 seconds", 726 | "0F":"Identification packet"} 727 | 728 | rfx_subtype_72 = {"00":"FS20", 729 | "01":"FHT8V valve", 730 | "02":"FHT80 door/window sensor"} 731 | 732 | rfx_subtype_7F = {"00":"Raw transmit"} 733 | 734 | def __getitem__(self, key): return self[key] 735 | def keys(self): return self.keys() 736 | -------------------------------------------------------------------------------- /lib/rfx_socket.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | # ------------------------------------------------------------------------------ 5 | # 6 | # RFX_SOCKET.PY 7 | # 8 | # Copyright (C) 2012-2013 Olivier Djian, 9 | # Sebastian Sjoholm, sebastian.sjoholm@gmail.com 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program. If not, see . 23 | # 24 | # Website: http://code.google.com/p/rfxcmd/ 25 | # 26 | # $Rev: 365 $ 27 | # $Date: 2013-03-24 12:58:25 +0100 (Sun, 24 Mar 2013) $ 28 | # 29 | # ------------------------------------------------------------------------------ 30 | 31 | import time 32 | import logging 33 | import threading 34 | 35 | from Queue import Queue 36 | messageQueue = Queue() 37 | 38 | import SocketServer 39 | SocketServer.TCPServer.allow_reuse_address = True 40 | from SocketServer import (TCPServer, StreamRequestHandler) 41 | 42 | # Import WEEWX extension 43 | try: 44 | from lib.rfx_weewx import * 45 | except ImportError: 46 | print "Error: module lib/weewx.py not found" 47 | sys.exit(1) 48 | 49 | logger = logging.getLogger('rfxcmd') 50 | 51 | # ------------------------------------------------------------------------------ 52 | 53 | class NetRequestHandler(StreamRequestHandler): 54 | 55 | def handle(self): 56 | logger.debug("Client connected to [%s:%d]" % self.client_address) 57 | lg = self.rfile.readline() 58 | messageQueue.put(lg) 59 | logger.debug("Message read from socket: " + lg.strip()) 60 | 61 | # WEEWX incoming string 62 | if lg.strip() == '0A1100FF001100FF001100': 63 | logger.debug("WeeWx request, send data to WeeWx") 64 | try: 65 | self.wfile.write("Received request weewx weatherstation - ok\n") 66 | self.wfile.write(wwx.weewx_result() + '\n') 67 | self.wfile.write("Sent result - ok\n") 68 | except Exception, e: 69 | logger.debug("Error: WeeWx data send failed") 70 | logger.debug("Error: %s" % str(e)) 71 | pass 72 | 73 | # WEEWX v2 74 | if lg[0:5] == "WEEWX": 75 | logger.debug("Process WeeWx request") 76 | indata = lg.split(';') 77 | logger.debug("Indata[0]: %s" % str(indata[0].strip())) 78 | logger.debug("Indata[1]: %s" % str(indata[1].strip())) 79 | 80 | # Sensor 0x4f 81 | if indata[1].strip() == "0x4f": 82 | logger.debug("Send WeeWx data for sensor 0x4f") 83 | self.wfile.write(wwx.weewx_0x4f()) 84 | 85 | # Sensor 0x50 86 | if indata[1].strip() == "0x50": 87 | logger.debug("Send WeeWx data for sensor 0x50") 88 | self.wfile.write(wwx.weewx_0x50()) 89 | 90 | # Sensor 0x51 91 | if indata[1].strip() == "0x51": 92 | logger.debug("Send WeeWx data for sensor 0x51") 93 | self.wfile.write(wwx.weewx_0x51()) 94 | 95 | # Sensor 0x52 96 | if indata[1].strip() == "0x52": 97 | logger.debug("Send WeeWx data for sensor 0x52") 98 | self.wfile.write(wwx.weewx_0x52()) 99 | 100 | # Sensor 0x53 101 | if indata[1].strip() == "0x53": 102 | logger.debug("Send WeeWx data for sensor 0x53") 103 | self.wfile.write(wwx.weewx_0x53()) 104 | 105 | # Sensor 0x54 106 | if indata[1].strip() == "0x54": 107 | logger.debug("Send WeeWx data for sensor 0x54") 108 | self.wfile.write(wwx.weewx_0x54()) 109 | 110 | # Sensor 0x55 111 | if indata[1].strip() == "0x55": 112 | logger.debug("Send WeeWx data for sensor 0x55") 113 | self.wfile.write(wwx.weewx_0x55()) 114 | 115 | # Sensor 0x56 116 | if indata[1].strip() == "0x56": 117 | logger.debug("Send WeeWx data for sensor 0x56") 118 | self.wfile.write(wwx.weewx_0x56()) 119 | 120 | # Sensor 0x57 121 | if indata[1].strip() == "0x57": 122 | logger.debug("Send WeeWx data for sensor 0x57") 123 | self.wfile.write(wwx.weewx_0x57()) 124 | 125 | self.netAdapterClientConnected = False 126 | logger.debug("Client disconnected from [%s:%d]" % self.client_address) 127 | 128 | class RFXcmdSocketAdapter(object, StreamRequestHandler): 129 | def __init__(self, address='localhost', port=55000): 130 | self.Address = address 131 | self.Port = port 132 | 133 | self.netAdapter = TCPServer((self.Address, self.Port), NetRequestHandler) 134 | if self.netAdapter: 135 | self.netAdapterRegistered = True 136 | threading.Thread(target=self.loopNetServer, args=()).start() 137 | 138 | def loopNetServer(self): 139 | logger.debug("LoopNetServer Thread started") 140 | logger.debug("Listening on: [%s:%d]" % (self.Address, self.Port)) 141 | self.netAdapter.serve_forever() 142 | logger.debug("LoopNetServer Thread stopped") 143 | 144 | # ------------------------------------------------------------------------------ 145 | # END 146 | # ------------------------------------------------------------------------------ 147 | -------------------------------------------------------------------------------- /lib/rfx_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=UTF-8 3 | 4 | # ------------------------------------------------------------------------------ 5 | # 6 | # RFX_UTILS.PY 7 | # 8 | # Copyright (C) 2012-2013 Sebastian Sjoholm, sebastian.sjoholm@gmail.com 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program 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 General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | # 23 | # Version history can be found at 24 | # http://code.google.com/p/rfxcmd/wiki/VersionHistory 25 | # 26 | # $Rev: 464 $ 27 | # $Date: 2013-05-01 22:41:36 +0200 (Wed, 01 May 2013) $ 28 | # 29 | # ------------------------------------------------------------------------------ 30 | 31 | # -------------------------------------------------------------------------- 32 | 33 | def stripped(str): 34 | """ 35 | Strip all characters that are not valid 36 | Credit: http://rosettacode.org/wiki/Strip_control_codes_and_extended_characters_from_a_string 37 | """ 38 | return "".join([i for i in str if ord(i) in range(32, 127)]) 39 | 40 | # -------------------------------------------------------------------------- 41 | 42 | def ByteToHex( byteStr ): 43 | """ 44 | Convert a byte string to it's hex string representation e.g. for output. 45 | http://code.activestate.com/recipes/510399-byte-to-hex-and-hex-to-byte-string-conversion/ 46 | 47 | Added str() to byteStr in case input data is in integer 48 | """ 49 | return ''.join( [ "%02X " % ord( x ) for x in str(byteStr) ] ).strip() 50 | 51 | # ---------------------------------------------------------------------------- 52 | 53 | def dec2bin(x, width=8): 54 | """ 55 | Base-2 (Binary) Representation Using Python 56 | http://stackoverflow.com/questions/187273/base-2-binary-representation-using-python 57 | Brian (http://stackoverflow.com/users/9493/brian) 58 | """ 59 | return ''.join(str((x>>i)&1) for i in xrange(width-1,-1,-1)) 60 | 61 | # ---------------------------------------------------------------------------- 62 | 63 | def testBit(int_type, offset): 64 | """ 65 | testBit() returns a nonzero result, 2**offset, if the bit at 'offset' is one. 66 | http://wiki.python.org/moin/BitManipulation 67 | """ 68 | mask = 1 << offset 69 | return(int_type & mask) 70 | 71 | # ---------------------------------------------------------------------------- 72 | 73 | def clearBit(int_type, offset): 74 | """ 75 | clearBit() returns an integer with the bit at 'offset' cleared. 76 | http://wiki.python.org/moin/BitManipulation 77 | """ 78 | mask = ~(1 << offset) 79 | return(int_type & mask) 80 | 81 | # ---------------------------------------------------------------------------- 82 | 83 | def split_len(seq, length): 84 | """ 85 | Split string into specified chunks. 86 | """ 87 | return [seq[i:i+length] for i in range(0, len(seq), length)] 88 | 89 | # ---------------------------------------------------------------------------- 90 | -------------------------------------------------------------------------------- /lib/rfx_weewx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | # ------------------------------------------------------------------------------ 5 | # 6 | # RFX_WEEWX.PY 7 | # 8 | # Copyright (C) 2013 M. Bakker 9 | # 10 | # Class weewx_data, needed for sending answer at request of 11 | # WEEWX Weatherstation software 12 | # 13 | # This program is free software: you can redistribute it and/or modify 14 | # it under the terms of the GNU General Public License as published by 15 | # the Free Software Foundation, either version 3 of the License, or 16 | # (at your option) any later version. 17 | # 18 | # This program is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with this program. If not, see . 25 | # 26 | # Website 27 | # http://code.google.com/p/rfxcmd/ 28 | # 29 | # $Rev: 464 $ 30 | # $Date: 2013-05-01 22:41:36 +0200 (Wed, 01 May 2013) $ 31 | # 32 | # ------------------------------------------------------------------------------ 33 | 34 | # -------------------------------------------------------------------------- 35 | 36 | import logging 37 | logger = logging.getLogger('rfxcmd') 38 | 39 | class weewx_data: 40 | def __init__( 41 | self, 42 | wwx_wind_dir = 0, 43 | wwx_wind_avg = 0, 44 | wwx_wind_gust = 0, 45 | wwx_wind_batt = 0, 46 | wwx_wind_sign = 0, 47 | wwx_th_t_out = 0, 48 | wwx_th_h_out = 0, 49 | wwx_th_hs_out = 0, 50 | wwx_th_batt = 0, 51 | wwx_th_sign = 0, 52 | wwx_thb_t_in = 0, 53 | wwx_thb_h_in = 0, 54 | wwx_thb_hs_in = 0, 55 | wwx_thb_b_in = 0, 56 | wwx_thb_fs_in = 0, 57 | wwx_thb_batt = 0, 58 | wwx_thb_sign = 0, 59 | wwx_rain_rate = 0, 60 | wwx_rain_batt = 0, 61 | wwx_rain_sign = 0, 62 | wwx_uv_out = 0, 63 | wwx_uv_batt = 0, 64 | wwx_uv_sign = 0, 65 | # 0x4F Temperature and Rain sensor 66 | wwx_0x4f_temp = 0, 67 | wwx_0x4f_raintotal = 0, 68 | wwx_0x4f_batt = 0, 69 | wwx_0x4f_rssi = 0, 70 | # 0x50 Temperature sensor 71 | wwx_0x50_temp = 0, 72 | wwx_0x50_batt = 0, 73 | wwx_0x50_rssi = 0, 74 | # 0x51 Humidity sensor 75 | wwx_0x51_hum = 0, 76 | wwx_0x51_batt = 0, 77 | wwx_0x51_rssi = 0, 78 | # 0x52 Temperature and Barometric sensor 79 | wwx_0x52_temp = 0, 80 | wwx_0x52_hum = 0, 81 | wwx_0x52_batt = 0, 82 | wwx_0x52_rssi = 0, 83 | # 0x53 Barometric sensor 84 | wwx_0x53_baro = 0, 85 | wwx_0x53_batt = 0, 86 | wwx_0x53_rssi = 0, 87 | # 0x54 Temp/Hum/Baro sensor 88 | wwx_0x54_temp = 0, 89 | wwx_0x54_hum = 0, 90 | wwx_0x54_baro = 0, 91 | wwx_0x54_batt = 0, 92 | wwx_0x54_rssi = 0, 93 | # 0x55 Rain sensor 94 | wwx_0x55_rainrate = 0, 95 | wwx_0x55_raintotal = 0, 96 | wwx_0x55_batt = 0, 97 | wwx_0x55_rssi = 0, 98 | # 0x56 Wind sensor 99 | wwx_0x56_direction = 0, 100 | wwx_0x56_avspeed = 0, 101 | wwx_0x56_gust = 0, 102 | wwx_0x56_temp = 0, 103 | wwx_0x56_chill = 0, 104 | wwx_0x56_batt = 0, 105 | wwx_0x56_rssi = 0, 106 | # 0x57 UV sensor 107 | wwx_0x57_uv = 0, 108 | wwx_0x57_temp = 0, 109 | wwx_0x57_batt = 0, 110 | wwx_0x57_rssi = 0 111 | ): 112 | 113 | self.wwx_wind_dir = wwx_wind_dir 114 | self.wwx_wind_avg = wwx_wind_avg 115 | self.wwx_wind_gust = wwx_wind_gust 116 | self.wwx_wind_batt = wwx_wind_batt 117 | self.wwx_wind_sign = wwx_wind_sign 118 | self.wwx_th_t_out = wwx_th_t_out 119 | self.wwx_th_h_out = wwx_th_h_out 120 | self.wwx_th_hs_out = wwx_th_hs_out 121 | self.wwx_th_batt = wwx_th_batt 122 | self.wwx_th_sign = wwx_th_sign 123 | self.wwx_thb_t_in = wwx_thb_t_in 124 | self.wwx_thb_h_in = wwx_thb_h_in 125 | self.wwx_thb_hs_in = wwx_thb_hs_in 126 | self.wwx_thb_b_in = wwx_thb_b_in 127 | self.wwx_thb_fs_in = wwx_thb_fs_in 128 | self.wwx_thb_batt = wwx_thb_batt 129 | self.wwx_thb_sign = wwx_thb_sign 130 | self.wwx_rain_rate = wwx_rain_rate 131 | self.wwx_rain_batt = wwx_rain_batt 132 | self.wwx_rain_sign = wwx_rain_sign 133 | self.wwx_uv_out = wwx_uv_out 134 | self.wwx_uv_batt = wwx_uv_batt 135 | self.wwx_uv_sign = wwx_uv_sign 136 | 137 | # 0x4F Temperature and Rain sensor 138 | self.wwx_0x4f_temp = wwx_0x4f_temp 139 | self.wwx_0x4f_raintotal = wwx_0x4f_raintotal 140 | self.wwx_0x4f_batt = wwx_0x4f_batt 141 | self.wwx_0x4f_rssi = wwx_0x4f_rssi 142 | 143 | # 0x50 Temperature sensor 144 | self.wwx_0x50_temp = wwx_0x50_temp 145 | self.wwx_0x50_batt = wwx_0x50_batt 146 | self.wwx_0x50_rssi = wwx_0x50_rssi 147 | 148 | # 0x51 Humidity sensor 149 | self.wwx_0x51_hum = wwx_0x51_hum 150 | self.wwx_0x51_batt = wwx_0x51_batt 151 | self.wwx_0x51_rssi = wwx_0x51_rssi 152 | 153 | # 0x52 Temperature and Barometric sensor 154 | self.wwx_0x52_temp = wwx_0x52_temp 155 | self.wwx_0x52_hum = wwx_0x52_hum 156 | self.wwx_0x52_batt = wwx_0x52_batt 157 | self.wwx_0x52_rssi = wwx_0x52_rssi 158 | 159 | # 0x53 Barometric sensor 160 | self.wwx_0x53_baro = wwx_0x53_baro 161 | self.wwx_0x53_batt = wwx_0x53_batt 162 | self.wwx_0x53_rssi = wwx_0x53_rssi 163 | 164 | # 0x54 Temp/Hum/Baro sensor 165 | self.wwx_0x54_temp = wwx_0x54_temp 166 | self.wwx_0x54_hum = wwx_0x54_hum 167 | self.wwx_0x54_baro = wwx_0x54_baro 168 | self.wwx_0x54_batt = wwx_0x54_batt 169 | self.wwx_0x54_rssi = wwx_0x54_rssi 170 | 171 | # 0x55 Rain sensor 172 | self.wwx_0x55_rainrate = wwx_0x55_rainrate 173 | self.wwx_0x55_raintotal = wwx_0x55_raintotal 174 | self.wwx_0x55_batt = wwx_0x55_batt 175 | self.wwx_0x55_rssi = wwx_0x55_rssi 176 | 177 | # 0x56 Wind sensor 178 | self.wwx_0x56_direction= wwx_0x56_direction 179 | self.wwx_0x56_avspeed = wwx_0x56_avspeed 180 | self.wwx_0x56_temp = wwx_0x56_temp 181 | self.wwx_0x56_gust = wwx_0x56_gust 182 | self.wwx_0x56_chill = wwx_0x56_chill 183 | self.wwx_0x56_batt = wwx_0x56_batt 184 | self.wwx_0x56_rssi = wwx_0x56_rssi 185 | 186 | # 0x57 UV Sensor 187 | self.wwx_0x57_uv = wwx_0x57_uv 188 | self.wwx_0x57_temp = wwx_0x57_temp 189 | self.wwx_0x57_batt = wwx_0x57_batt 190 | self.wwx_0x57_rssi = wwx_0x57_rssi 191 | 192 | def weewx_result(self): 193 | result = '|' 194 | result = result + str(wwx.wwx_wind_dir) + '|' + str(wwx.wwx_wind_avg) + '|' + str(wwx.wwx_wind_gust) 195 | result = result + '|' + str(wwx.wwx_wind_batt) + '|' + str(wwx.wwx_wind_sign) 196 | result = result + '|' + str(wwx.wwx_th_t_out) + '|' + str(wwx.wwx_th_h_out) + '|' + str(wwx.wwx_th_hs_out) 197 | result = result + '|' + str(wwx.wwx_th_batt) + '|' + str(wwx.wwx_th_sign) 198 | result = result + '|' + str(wwx.wwx_thb_t_in) + '|' + str(wwx.wwx_thb_h_in) 199 | result = result + '|' + str(wwx.wwx_thb_hs_in) + '|' + str(wwx.wwx_thb_b_in) 200 | result = result + '|' + str(wwx.wwx_thb_fs_in) + '|' + str(wwx.wwx_thb_batt) + '|' + str(wwx.wwx_thb_sign) 201 | result = result + '|' + str(wwx.wwx_rain_rate) + '|' + str(wwx.wwx_rain_batt) + '|' + str(wwx.wwx_rain_sign) 202 | result = result + '|' + str(wwx.wwx_uv_out) + '|' + str(wwx.wwx_uv_batt) + '|' + str(wwx.wwx_uv_sign) 203 | result = result + '|' 204 | return result 205 | 206 | # 0x52 Temp/Hum Sensor 207 | def weewx_0x52(self): 208 | result = None 209 | result = "%s;%s;%s;%s" % (str(wwx.wwx_0x52_temp),str(wwx.wwx_0x52_hum),str(wwx.wwx_0x52_batt),str(wwx.wwx_0x52_rssi)) 210 | logger.debug("Weewx.0x52=%s" % str(result)) 211 | return result 212 | 213 | # 0x53 Barometric Sensor 214 | def weewx_0x53(self): 215 | result = None 216 | result = "%s;%s;%s" % (str(wwx.wwx_0x53_baro),str(wwx.wwx_0x53_batt),str(wwx.wwx_0x53_rssi)) 217 | logger.debug("Weewx.0x53=%s" % str(result)) 218 | return result 219 | 220 | # 0x54 Temp/Hum/Baro Sensor 221 | def weewx_0x54(self): 222 | result = None 223 | result = "%s;%s;%s;%s;%s" % (str(wwx.wwx_0x54_temp),str(wwx.wwx_0x54_hum),str(wwx.wwx_0x54_baro),str(wwx.wwx_0x54_batt),str(wwx.wwx_0x54_rssi)) 224 | logger.debug("Weewx.0x54=%s" % str(result)) 225 | return result 226 | 227 | # 0x55 Rain Sensor 228 | def weewx_0x55(self): 229 | result = None 230 | result = "%s;%s;%s;%s" % (str(wwx.wwx_0x55_rainrate),str(wwx.wwx_0x55_raintotal),str(wwx.wwx_0x55_batt),str(wwx.wwx_0x55_rssi)) 231 | logger.debug("Weewx.0x55=%s" % str(result)) 232 | return result 233 | 234 | # 0x56 Wind Sensor 235 | def weewx_0x56(self): 236 | result = None 237 | result = "%s;%s;%s;%s;%s;%s;%s" % (str(wwx.wwx_0x56_direction),str(wwx.wwx_0x56_avspeed),str(wwx.wwx_0x56_temp),str(wwx.wwx_0x56_gust),str(wwx.wwx_0x56_chill),str(wwx.wwx_0x56_batt),str(wwx.wwx_0x56_rssi)) 238 | logger.debug("Weewx.0x56=%s" % str(result)) 239 | return result 240 | 241 | # 0x57 UV Sensor 242 | def weewx_0x57(self): 243 | result = None 244 | result = "%s;%s;%s;%s" % (str(wwx.wwx_0x57_uv),str(wwx.wwx_0x57_temp),str(wwx.wwx_0x57_batt),str(wwx.wwx_0x57_rssi)) 245 | logger.debug("Weewx.0x57=%s" % str(result)) 246 | return result 247 | 248 | wwx = weewx_data() 249 | 250 | # -------------------------------------------------------------------------- 251 | -------------------------------------------------------------------------------- /lib/rfx_xplcom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=UTF-8 3 | 4 | # ----------------------------------------------------------------------------- 5 | # 6 | # RFX_XPLCOM.PY 7 | # 8 | # Copyright (C) 2012-2013 Sebastian Sjoholm, sebastian.sjoholm@gmail.com 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program 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 General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | # 23 | # Website 24 | # http://code.google.com/p/rfxcmd/ 25 | # 26 | # $Rev: 288 $ 27 | # $Date: 2013-02-07 04:55:54 +0100 (Thu, 07 Feb 2013) $ 28 | # 29 | # ----------------------------------------------------------------------------- 30 | 31 | import sys 32 | import string 33 | import select 34 | import socket 35 | import datetime 36 | 37 | # ----------------------------------------------------------------------------- 38 | 39 | def send(host, message, sourcename = "rfxcmd-", hostname = True): 40 | """ 41 | Send data to XPL network 42 | Credit: Jean-Louis Bergamo 43 | """ 44 | 45 | sock = None 46 | addr = (host,3865) 47 | 48 | try: 49 | sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 50 | except socket.error as msg: 51 | sock = None 52 | 53 | try: 54 | sock.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1) 55 | except socket.error as msg: 56 | sock.close() 57 | sock = None 58 | 59 | if sock is None: 60 | print 'could not open socket' 61 | 62 | if hostname: 63 | sourcename = sourcename + socket.gethostname() 64 | 65 | #message = 'xpl-stat\n{\nhop=1\nsource=rfxcmd.'+hostname+'\ntarget=*\n}\nsensor.basic\n{\n' + message + '\n}\n' 66 | message = 'xpl-stat\n{\nhop=1\nsource='+sourcename+'\ntarget=*\n}\nsensor.basic\n{\n' + message + '\n}\n' 67 | sock.sendto(message,addr) 68 | sock.close() 69 | 70 | # ----------------------------------------------------------------------------- 71 | 72 | def SendHeartbeat(port): 73 | """ 74 | Send heartbeat 75 | Based on John Bent xPL Monitor for Python 76 | http://www.xplproject.org.uk/ 77 | """ 78 | sock = None 79 | 80 | try: 81 | sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 82 | except socket.error as msg: 83 | sock = None 84 | 85 | try: 86 | sock.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1) 87 | except socket.error as msg: 88 | sock.close() 89 | sock = None 90 | 91 | hostname = socket.gethostname() 92 | message = "xpl-stat\n{\nhop=1\nsource=xplmon."+hostname+"\ntarget=*\n}\nhbeat.app\n{\ninterval=5\nport=" + str(port) + "\n}\n" 93 | sock.sendto(message,("255.255.255.255",3865)) 94 | 95 | # ----------------------------------------------------------------------------- 96 | 97 | def listen(): 98 | """ 99 | Listen to xPL messages and print them to stdout with timestamps 100 | Based on John Bent xPL Monitor for Python 101 | http://www.xplproject.org.uk/ 102 | """ 103 | 104 | # Define maximum xPL message size 105 | buff = 1500 106 | 107 | port = 3865 108 | 109 | # Initialise the socket 110 | UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 111 | addr = ("0.0.0.0",port) 112 | 113 | # Try and bind to the base port 114 | try: 115 | UDPSock.bind(addr) 116 | except: 117 | # A hub is running, so bind to a high port 118 | port = 50000 119 | 120 | addr = ("127.0.0.1",port) 121 | try: 122 | UDPSock.bind(addr) 123 | except: 124 | port += 1 125 | 126 | print "xPLMon, bound to port " + str(port) + ", exit with ctrl+c" 127 | 128 | SendHeartbeat(port) 129 | 130 | try: 131 | while 1==1: 132 | readable, writeable, errored = select.select([UDPSock],[],[],60) 133 | 134 | if len(readable) == 1: 135 | now = datetime.datetime.now() 136 | data,addr = UDPSock.recvfrom(buff) 137 | data = data.replace('\n', ' ') 138 | print now.strftime("%Y:%m:%d %H:%M:%S") + "\t" + data 139 | 140 | except KeyboardInterrupt: 141 | print "\nExit..." 142 | pass 143 | 144 | if __name__ == '__main__': 145 | listen() 146 | -------------------------------------------------------------------------------- /protocol.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0 4 | Undecoded 5 | 0 6 | 7 | 8 | 1 9 | RFU 10 | 0 11 | 12 | 13 | 2 14 | Byron SX 15 | 0 16 | 17 | 18 | 3 19 | RSL 20 | 0 21 | 22 | 23 | 4 24 | Lightning4 25 | 0 26 | 27 | 28 | 5 29 | FineOffset / Viking 30 | 0 31 | 32 | 33 | 6 34 | Rubicson 35 | 0 36 | 37 | 38 | 7 39 | AE Blyss 40 | 0 41 | 42 | no 43 | 44 | 8 45 | Blinds T1/T2/T3/T4 46 | 0 47 | 48 | 49 | 9 50 | Blinds T0 51 | 0 52 | 53 | 54 | 10 55 | ProGuard 56 | 0 57 | 58 | 59 | 11 60 | FS20 61 | 0 62 | 63 | 64 | 12 65 | La Crosse 66 | 0 67 | 68 | 69 | 13 70 | Hideki / UPM 71 | 0 72 | 73 | 74 | 14 75 | AD Lightwave RF 76 | 0 77 | 78 | 79 | 15 80 | Mertik 81 | 0 82 | 83 | 84 | 16 85 | Visonic 86 | 0 87 | 88 | 89 | 17 90 | ATI 91 | 0 92 | 93 | 94 | 18 95 | Oregon Scientific 96 | 0 97 | 98 | 99 | 19 100 | Meiantech 101 | 0 102 | 103 | 104 | 20 105 | HomeEasy EU 106 | 0 107 | 108 | 109 | 21 110 | AC 111 | 0 112 | 113 | 114 | 22 115 | ARC 116 | 0 117 | 118 | 119 | 23 120 | X10 121 | 0 122 | 123 | -------------------------------------------------------------------------------- /rfxdaemon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # rfxcmd daemon 3 | # chkconfig: 345 20 80 4 | # description: rfxcmd daemon 5 | # processname: rfxcmd.py 6 | 7 | ############################################################################## 8 | # 9 | # Note, you might need to change the parameters below, the current settings 10 | # are for default setup. 11 | # 12 | ############################################################################## 13 | 14 | DAEMON_PATH="/opt/rfxcmd/" 15 | SERIAL_DEVICE="/dev/rfxcom" 16 | CONFIG_FILE="/opt/rfxcmd/config.xml" 17 | 18 | DAEMON=rfxcmd.py 19 | DAEMONOPTS="-l -o $CONFIG_FILE $OTHER_SWITCH" 20 | 21 | NAME=rfxcmd 22 | DESC="rfxcmd daemon startup script" 23 | PIDFILE=/var/run/$NAME.pid 24 | SCRIPTNAME=/etc/init.d/$NAME 25 | 26 | case "$1" in 27 | start) 28 | printf "%-50s" "Starting $NAME..." 29 | # Check if PID exists, and if process is active 30 | if [ -f $PIDFILE ]; then 31 | PID=`cat $PIDFILE` 32 | if [ -z "`ps axf | grep ${PID} | grep -v grep`" ]; then 33 | # Process not active remove PID file 34 | rm -f $PIDFILE 35 | else 36 | printf "%s\n" "Process already started..." 37 | exit 1 38 | fi 39 | fi 40 | 41 | $DAEMON_PATH$DAEMON $DAEMONOPTS 42 | 43 | # Check process 44 | PID=`cat $PIDFILE` 45 | if [ -f $PID ]; then 46 | printf "%s\n" "Fail" 47 | else 48 | printf "%s\n" "Ok" 49 | fi 50 | ;; 51 | status) 52 | printf "%-50s" "Checking $NAME..." 53 | if [ -f $PIDFILE ]; then 54 | PID=`cat $PIDFILE` 55 | if [ -z "`ps axf | grep ${PID} | grep -v grep`" ]; then 56 | printf "%s\n" "Process dead but pidfile exists" 57 | else 58 | echo "Running" 59 | fi 60 | else 61 | printf "%s\n" "Service not running" 62 | fi 63 | ;; 64 | stop) 65 | printf "%-50s" "Stopping $NAME" 66 | PID=`cat $PIDFILE` 67 | cd $DAEMON_PATH 68 | if [ -f $PIDFILE ]; then 69 | kill -HUP $PID 70 | printf "%s\n" "Ok" 71 | rm -f $PIDFILE 72 | else 73 | printf "%s\n" "pidfile not found" 74 | fi 75 | ;; 76 | 77 | restart) 78 | $0 stop 79 | $0 start 80 | ;; 81 | 82 | *) 83 | echo "Usage: $0 {status|start|stop|restart}" 84 | exit 1 85 | esac 86 | -------------------------------------------------------------------------------- /rfxproto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=UTF-8 3 | 4 | # ------------------------------------------------------------------------------ 5 | # 6 | # RFXPROTO.PY 7 | # 8 | # Copyright (C) 2012-2013 Sebastian Sjoholm, sebastian.sjoholm@gmail.com 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program 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 General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | # 23 | # Website: http://code.google.com/p/rfxcmd/ 24 | # 25 | # $Rev: 566 $ 26 | # $Date: 2013-11-22 21:43:41 +0100 (Fri, 22 Nov 2013) $ 27 | # 28 | # NOTES 29 | # 30 | # RFXCOM is a Trademark of RFSmartLink. 31 | # 32 | # ------------------------------------------------------------------------------ 33 | # 34 | # Protocol License Agreement 35 | # 36 | # The RFXtrx protocols are owned by RFXCOM, and are protected under applicable 37 | # copyright laws. 38 | # 39 | # ============================================================================== 40 | # It is only allowed to use this protocol or any part of it for RFXCOM products 41 | # ============================================================================== 42 | # 43 | # The above Protocol License Agreement and the permission notice shall be 44 | # included in all software using the RFXtrx protocols. 45 | # 46 | # Any use in violation of the foregoing restrictions may subject the user to 47 | # criminal sanctions under applicable laws, as well as to civil liability for 48 | # the breach of the terms and conditions of this license. 49 | # 50 | # ------------------------------------------------------------------------------ 51 | 52 | __author__ = "Sebastian Sjoholm" 53 | __copyright__ = "Copyright 2012-2014, Sebastian Sjoholm" 54 | __license__ = "GPL" 55 | __version__ = "0.1 (" + filter(str.isdigit, "$Rev: 566 $") + ")" 56 | __maintainer__ = "Sebastian Sjoholm" 57 | __email__ = "sebastian.sjoholm@gmail.com" 58 | __status__ = "Development" 59 | __date__ = "$Date: 2013-11-22 21:43:41 +0100 (Fri, 22 Nov 2013) $" 60 | 61 | # Default modules 62 | import logging 63 | import sys 64 | import string 65 | import optparse 66 | import time 67 | import binascii 68 | import fcntl 69 | 70 | # Serial 71 | try: 72 | import serial 73 | except ImportError as err: 74 | print("Error: %s" % str(err)) 75 | sys.exit(1) 76 | 77 | # ---------------------------------------------------------------------------- 78 | 79 | class rfxcmd_data: 80 | def __init__( 81 | self, 82 | reset = "0d00000000000000000000000000", 83 | status = "0d00000002000000000000000000", 84 | save = "0d00000006000000000000000000" 85 | ): 86 | 87 | self.reset = reset 88 | self.status = status 89 | self.save = save 90 | 91 | class serial_data: 92 | def __init__( 93 | self, 94 | device = None, 95 | port = None, 96 | rate = 38400, 97 | timeout = 9 98 | ): 99 | 100 | self.device = device 101 | self.port = port 102 | self.rate = rate 103 | self.timeout = timeout 104 | 105 | # ---------------------------------------------------------------------------- 106 | 107 | """ 108 | Bit manipulation 109 | Source: http://wiki.python.org/moin/BitManipulation 110 | """ 111 | 112 | def testBit(int_type, offset): 113 | mask = 1 << offset 114 | return(int_type & mask) 115 | 116 | def setBit(int_type, offset): 117 | mask = 1 << offset 118 | return(int_type | mask) 119 | 120 | def clearBit(int_type, offset): 121 | mask = ~(1 << offset) 122 | return(int_type & mask) 123 | 124 | def toggleBit(int_type, offset): 125 | mask = 1 << offset 126 | return(int_type ^ mask) 127 | 128 | # ---------------------------------------------------------------------------- 129 | 130 | """ 131 | Convert a byte string to it's hex string representation e.g. for output. 132 | Added str() to byteStr in case input data is in integer 133 | Credit: http://code.activestate.com/recipes/510399-byte-to-hex-and-hex-to-byte-string-conversion/ 134 | """ 135 | def ByteToHex( byteStr ): 136 | return ''.join( [ "%02X " % ord( x ) for x in str(byteStr) ] ).strip() 137 | 138 | # ---------------------------------------------------------------------------- 139 | 140 | """ 141 | Strip all characters that are not valid 142 | Credit: http://rosettacode.org/wiki/Strip_control_codes_and_extended_characters_from_a_string 143 | """ 144 | def stripped(str): 145 | return "".join([i for i in str if ord(i) in range(32, 127)]) 146 | 147 | # ----------------------------------------------------------------------------- 148 | 149 | def print_version(): 150 | print "RFXPROTO Version: " + __version__ 151 | print __date__.replace('$', '') 152 | return 153 | 154 | # ----------------------------------------------------------------------------- 155 | 156 | def readbytes(number): 157 | buf = '' 158 | for i in range(number): 159 | try: 160 | byte = s.device.read() 161 | except IOError as err: 162 | print("Error: %s" % str(err)) 163 | return False 164 | except OSError as err: 165 | print("Error: %s" % str(err)) 166 | return False 167 | buf += byte 168 | return buf 169 | 170 | # ----------------------------------------------------------------------------- 171 | 172 | def rfx_setmode(protocol, state): 173 | 174 | try: 175 | 176 | result = rfx_sendmsg(r.status) 177 | logger.debug("Result: %s" % str(ByteToHex(result))) 178 | if result: 179 | 180 | bstr = rfx_decode(result) 181 | logger.debug("Binary: %s" % str(bstr)) 182 | 183 | # Change 184 | bstr[protocol] = str(state) 185 | logger.debug("Binary: %s" % str(bstr)) 186 | 187 | # Complete message 188 | msg3 = bstr[0] + bstr[1] + bstr[2] + bstr[3] + bstr[4] + bstr[5] + bstr[6] + bstr[7] 189 | msg3_int = int(msg3,2) 190 | msg3_hex = hex(msg3_int)[2:].zfill(2) 191 | msg4 = bstr[8] + bstr[9] + bstr[10] + bstr[11] + bstr[12] + bstr[13] + bstr[14] + bstr[15] 192 | msg4_int = int(msg4,2) 193 | msg4_hex = hex(msg4_int)[2:].zfill(2) 194 | msg5 = bstr[16] + bstr[17] + bstr[18] + bstr[19] + bstr[20] + bstr[21] + bstr[22] + bstr[23] 195 | msg5_int = int(msg5,2) 196 | msg5_hex = hex(msg5_int)[2:].zfill(2) 197 | logger.debug("msg3: %s / %s" % (str(msg3), msg3_hex)) 198 | logger.debug("msg4: %s / %s" % (str(msg4), msg4_hex)) 199 | logger.debug("msg5: %s / %s" % (str(msg5), msg5_hex)) 200 | 201 | # Send command 202 | command = "0D000000035300%s%s%s00000000" % (msg3_hex, msg4_hex, msg5_hex) 203 | logger.debug("Command: %s" % command.upper()) 204 | rfx_decodestatus(rfx_sendmsg(command.upper())) 205 | else: 206 | print("Invalid result received") 207 | return False 208 | 209 | except Exception as err: 210 | logger.debug("Error: Send failed, error: %s" % str(err)) 211 | print("Error: Could not send message: %s " % err) 212 | 213 | return True 214 | 215 | # ----------------------------------------------------------------------------- 216 | 217 | def rfx_decode(message): 218 | 219 | data = { 220 | 'packetlen' : ByteToHex(message[0]), 221 | 'packettype' : ByteToHex(message[1]), 222 | 'subtype' : ByteToHex(message[2]), 223 | 'seqnbr' : ByteToHex(message[3]), 224 | 'cmnd' : ByteToHex(message[4]), 225 | 'msg1' : ByteToHex(message[5]), 226 | 'msg2' : ByteToHex(message[6]), 227 | 'msg3' : ByteToHex(message[7]), 228 | 'msg4' : ByteToHex(message[8]), 229 | 'msg5' : ByteToHex(message[9]), 230 | 'msg6' : ByteToHex(message[10]), 231 | 'msg7' : ByteToHex(message[11]), 232 | 'msg8' : ByteToHex(message[12]), 233 | 'msg9' : ByteToHex(message[13]) 234 | } 235 | 236 | msg3_bin = binascii.a2b_hex(data['msg3']) 237 | msg3_binstr = "".join("{:08b}".format(ord(i)) for i in msg3_bin) 238 | msg4_bin = binascii.a2b_hex(data['msg4']) 239 | msg4_binstr = "".join("{:08b}".format(ord(i)) for i in msg4_bin) 240 | msg5_bin = binascii.a2b_hex(data['msg5']) 241 | msg5_binstr = "".join("{:08b}".format(ord(i)) for i in msg5_bin) 242 | 243 | return list(msg3_binstr + msg4_binstr + msg5_binstr) 244 | 245 | # ----------------------------------------------------------------------------- 246 | 247 | def rfx_decodestatus(message): 248 | 249 | logger.debug("Decode status message") 250 | 251 | data = { 252 | 'packetlen' : ByteToHex(message[0]), 253 | 'packettype' : ByteToHex(message[1]), 254 | 'subtype' : ByteToHex(message[2]), 255 | 'seqnbr' : ByteToHex(message[3]), 256 | 'cmnd' : ByteToHex(message[4]), 257 | 'msg1' : ByteToHex(message[5]), 258 | 'msg2' : ByteToHex(message[6]), 259 | 'msg3' : ByteToHex(message[7]), 260 | 'msg4' : ByteToHex(message[8]), 261 | 'msg5' : ByteToHex(message[9]), 262 | 'msg6' : ByteToHex(message[10]), 263 | 'msg7' : ByteToHex(message[11]), 264 | 'msg8' : ByteToHex(message[12]), 265 | 'msg9' : ByteToHex(message[13]) 266 | } 267 | 268 | print("RFX protocols") 269 | print("-------------------------------------------------------------------") 270 | print("#\tProtocol\t\t\tState") 271 | print("-------------------------------------------------------------------") 272 | 273 | # MSG 3 274 | 275 | if testBit(int(data['msg3'],16),7) == 128: 276 | print("0\tUndecoded\t\t\tEnabled") 277 | else: 278 | print("0\tUndecoded\t\t\tDisabled") 279 | 280 | if testBit(int(data['msg3'],16),6) == 64: 281 | print("1\tRFU\t\t\t\tEnabled") 282 | else: 283 | print("1\tRFU\t\t\t\tDisabled") 284 | 285 | if testBit(int(data['msg3'],16),5) == 32: 286 | print("2\tByrox SX\t\t\tEnabled") 287 | else: 288 | print("2\tByrox SX\t\t\tDisabled") 289 | 290 | if testBit(int(data['msg3'],16),4) == 16: 291 | print("3\tRSL\t\t\t\tEnabled") 292 | else: 293 | print("3\tRSL\t\t\t\tDisabled") 294 | 295 | if testBit(int(data['msg3'],16),3) == 8: 296 | print("4\tLightning4\t\t\tEnabled") 297 | else: 298 | print("4\tLightning4\t\t\tDisabled") 299 | 300 | if testBit(int(data['msg3'],16),2) == 4: 301 | print("5\tFineOffset/Viking\t\tEnabled") 302 | else: 303 | print("5\tFineOffset/Viking\t\tDisabled") 304 | 305 | if testBit(int(data['msg3'],16),1) == 2: 306 | print("6\tRubicson\t\t\tEnabled") 307 | else: 308 | print("6\tRubicson\t\t\tDisabled") 309 | 310 | if testBit(int(data['msg3'],16),0) == 1: 311 | print("7\tAE Blyss\t\t\tEnabled") 312 | else: 313 | print("7\tAE Blyss\t\t\tDisabled") 314 | 315 | # MSG 4 316 | if testBit(int(data['msg4'],16),6) == 128: 317 | print("8\tBlindsT1/T2/T3/T4\t\tEnabled") 318 | else: 319 | print("8\tBlindsT1/T2/T3/T4\t\tDisabled") 320 | 321 | if testBit(int(data['msg4'],16),6) == 64: 322 | print("9\tBlindsT0\t\t\tEnabled") 323 | else: 324 | print("9\tBlindsT0\t\t\tDisabled") 325 | 326 | if testBit(int(data['msg4'],16),5) == 32: 327 | print("10\tProGuard\t\t\tEnabled") 328 | else: 329 | print("10\tProGuard\t\t\tDisabled") 330 | 331 | if testBit(int(data['msg4'],16),4) == 16: 332 | print("11\tFS20\t\t\t\tEnabled") 333 | else: 334 | print("11\tFS20\t\t\t\tDisabled") 335 | 336 | if testBit(int(data['msg4'],16),3) == 8: 337 | print("12\tLa Crosse\t\t\tEnabled") 338 | else: 339 | print("12\tLa Crosse\t\t\tDisabled") 340 | 341 | if testBit(int(data['msg4'],16),2) == 4: 342 | print("13\tHideki/UPM\t\t\tEnabled") 343 | else: 344 | print("13\tHideki/UPM\t\t\tDisabled") 345 | 346 | if testBit(int(data['msg4'],16),1) == 2: 347 | print("14\tAD LightwaveRF\t\t\tEnabled") 348 | else: 349 | print("14\tAD LightwaveRF\t\t\tDisabled") 350 | 351 | if testBit(int(data['msg4'],16),0) == 1: 352 | print("15\tMertik\t\t\t\tEnabled") 353 | else: 354 | print("15\tMertik\t\t\t\tDisabled") 355 | 356 | # MSG 5 357 | if testBit(int(data['msg5'],16),6) == 128: 358 | print("16\tBlindsT1/T2/T3/T4\t\tEnabled") 359 | else: 360 | print("16\tBlindsT1/T2/T3/T4\t\tDisabled") 361 | 362 | if testBit(int(data['msg5'],16),6) == 64: 363 | print("17\tBlindsT0\t\t\tEnabled") 364 | else: 365 | print("17\tBlindsT0\t\t\tDisabled") 366 | 367 | if testBit(int(data['msg5'],16),5) == 32: 368 | print("18\tOregon Scientific\t\tEnabled") 369 | else: 370 | print("18\tOregon Scientific\t\tDisabled") 371 | 372 | if testBit(int(data['msg5'],16),4) == 16: 373 | print("19\tMeiantech\t\t\tEnabled") 374 | else: 375 | print("19\tMeiantech\t\t\tDisabled") 376 | 377 | if testBit(int(data['msg5'],16),3) == 8: 378 | print("20\tHomeEasy EU\t\t\tEnabled") 379 | else: 380 | print("20\tHomeEasy EU\t\t\tDisabled") 381 | 382 | if testBit(int(data['msg5'],16),2) == 4: 383 | print("21\tAC\t\t\t\tEnabled") 384 | else: 385 | print("21\tAC\t\t\t\tDisabled") 386 | 387 | if testBit(int(data['msg5'],16),1) == 2: 388 | print("22\tARC\t\t\t\tEnabled") 389 | else: 390 | print("22\tARC\t\t\t\tDisabled") 391 | 392 | if testBit(int(data['msg5'],16),0) == 1: 393 | print("23\tX10\t\t\t\tEnabled") 394 | else: 395 | print("23\tX10\t\t\t\tDisabled") 396 | 397 | return 398 | 399 | # ----------------------------------------------------------------------------- 400 | 401 | def rfx_sendmsg(message=None): 402 | 403 | if message == None: 404 | logger.debug("No message was specified") 405 | return False 406 | 407 | # Check that serial module is loaded 408 | try: 409 | logger.debug("Serial extension version: " + serial.VERSION) 410 | except: 411 | logger.debug("Error: Serial extension for Python could not be loaded") 412 | print("Error: You need to install Serial extension for Python") 413 | return False 414 | 415 | # Check for serial device 416 | if s.port: 417 | logger.debug("Device: " + s.port) 418 | else: 419 | logger.error("Device name missing") 420 | print("Serial device is missing") 421 | return False 422 | 423 | # Open serial port 424 | logger.debug("Open Serialport") 425 | try: 426 | s.device = serial.Serial(s.port, s.rate, timeout=s.timeout) 427 | except serial.SerialException as err: 428 | logger.debug("Error: Failed to connect on device, %s" % str(err)) 429 | print("Error: Failed to connect on device %s" % str(s.port)) 430 | print("Error: %s" % str(err)) 431 | return False 432 | 433 | # Flush buffer 434 | logger.debug("Serialport flush output") 435 | s.device.flushOutput() 436 | logger.debug("Serialport flush input") 437 | s.device.flushInput() 438 | 439 | # Send RESET 440 | logger.debug("Send RFX reset") 441 | s.device.write(r.reset.decode('hex')) 442 | time.sleep(1) 443 | 444 | # Flush buffer 445 | logger.debug("Serialport flush output") 446 | s.device.flushOutput() 447 | logger.debug("Serialport flush input") 448 | s.device.flushInput() 449 | 450 | logger.debug("Send message") 451 | s.device.write(message.decode('hex')) 452 | time.sleep(1) 453 | 454 | # Waiting reply 455 | result = None 456 | byte = None 457 | logger.debug("Wait for the reply") 458 | try: 459 | while 1: 460 | time.sleep(0.01) 461 | try: 462 | try: 463 | if s.device.inWaiting() != 0: 464 | timestamp = time.strftime("%Y-%m-%d %H:%M:%S") 465 | logger.debug("Timestamp: " + timestamp) 466 | logger.debug("SerWaiting: " + str(s.device.inWaiting())) 467 | byte = s.device.read() 468 | logger.debug("Byte: " + str(ByteToHex(byte))) 469 | except IOError, err: 470 | logger.error("Serial read error: %s" % str(err)) 471 | print("Serial error: " + str(err)) 472 | break 473 | 474 | try: 475 | if byte is not None: 476 | if ord(byte) == 13: 477 | result = byte + readbytes( ord(byte) ) 478 | logger.debug("Message: " + str(ByteToHex(result))) 479 | break 480 | else: 481 | logger.debug("Wrong message received") 482 | result = byte + readbytes( ord(byte) ) 483 | logger.debug("Message: " + str(ByteToHex(result))) 484 | print("Error: Wrong or no response received") 485 | sys.exit(1) 486 | 487 | except Exception as err: 488 | print("Error: %s" % str(err)) 489 | sys.exit(1) 490 | 491 | except OSError as err: 492 | logger.debug("Error in message: %s" % str(ByteToHex(message))) 493 | logger.debug("Error: %s" % str(err)) 494 | logger.debug("Traceback: " + traceback.format_exc()) 495 | print("Error: Serial error (%s) " % str(ByteToHex(message))) 496 | break 497 | 498 | except KeyboardInterrupt: 499 | logger.debug("Received keyboard interrupt") 500 | pass 501 | 502 | logger.debug("Close serial port") 503 | try: 504 | s.device.close() 505 | logger.debug("Serial port closed") 506 | except: 507 | logger.debug("Error: Failed to close the serial port (%s)" % str(s.port)) 508 | print("Error: Failed to close the port %s" % str(s.port)) 509 | return False 510 | 511 | return result 512 | 513 | # ----------------------------------------------------------------------------- 514 | 515 | if __name__ == '__main__': 516 | 517 | r = rfxcmd_data() 518 | s = serial_data() 519 | 520 | parser = optparse.OptionParser() 521 | parser.add_option("-d", "--device", action="store", type="string", dest="device", help="Serial device of the RFXtrx433") 522 | parser.add_option("-l", "--list", action="store_true", dest="list", help="List all protocols") 523 | parser.add_option("-p", "--protocol", action="store", type="string", dest="protocol", help="Protocol number") 524 | parser.add_option("-s", "--setstate", action="store", type="string", dest="state", help="Set protocol state (on or off)") 525 | parser.add_option("-v", "--save", action="store_true", dest="save", help="Save current settings in device (Note device have limited write cycles)") 526 | parser.add_option("-V", "--version", action="store_true", dest="version", help="Print rfxcmd version information") 527 | parser.add_option("-D", "--debug", action="store_true", dest="debug", help="Debug logging on screen") 528 | 529 | (options, args) = parser.parse_args() 530 | 531 | if options.debug: 532 | loglevel = logging.DEBUG 533 | else: 534 | loglevel = logging.ERROR 535 | 536 | # Logging 537 | formatter = logging.Formatter('%(asctime)s - %(threadName)s - %(module)s:%(lineno)d - %(levelname)s - %(message)s') 538 | handler = logging.StreamHandler() 539 | handler.setFormatter(formatter) 540 | logger = logging.getLogger("RFXPROTO") 541 | logger.setLevel(loglevel) 542 | logger.addHandler(handler) 543 | 544 | logger.debug("Logger started") 545 | logger.debug("Version: %s" % __version__) 546 | logger.debug("Date: %s" % __date__.replace('$', '')) 547 | 548 | if options.version: 549 | logger.debug("Print version") 550 | print_version() 551 | 552 | if options.device: 553 | s.port=options.device 554 | logger.debug("Serial device: %s" % str(s.port)) 555 | else: 556 | s.port=None 557 | logger.debug("Serial device: %s" % str(s.port)) 558 | 559 | # Set protocol state 560 | if options.protocol: 561 | pnum = int(options.protocol) 562 | if options.state == "on" or options.state == "off": 563 | if not s.port == None: 564 | logger.debug("Protocol num: %s" % str(pnum)) 565 | logger.debug("Protocol state: %s" % options.state) 566 | if options.state == "on": 567 | rfx_setmode(pnum, 1) 568 | else: 569 | rfx_setmode(pnum, 0) 570 | else: 571 | logger.debug("Error: Serial device is missing") 572 | print("Error: Serial device is missing") 573 | else: 574 | logger.debug("Error: Unknown state parameter for protocol") 575 | print("Error: Unknown state parameter for protocol") 576 | 577 | # Get current status 578 | if options.list: 579 | if not s.port == None: 580 | logger.debug("Get current state") 581 | try: 582 | result = rfx_sendmsg(r.status) 583 | logger.debug("Result: %s" % str(ByteToHex(result))) 584 | if result: 585 | rfx_decodestatus(result) 586 | else: 587 | print("Invalid result received") 588 | except Exception as err: 589 | logger.debug("Error: Send failed, error: %s" % str(err)) 590 | print("Error: Could not send message: %s " % err) 591 | else: 592 | logger.debug("Error: Serial device is missing") 593 | print("Error: Serial device is missing") 594 | 595 | logger.debug("Exit") 596 | sys.exit(0) 597 | 598 | # ------------------------------------------------------------------------------ 599 | # END 600 | # ------------------------------------------------------------------------------ 601 | -------------------------------------------------------------------------------- /rfxsend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=UTF-8 3 | 4 | # ------------------------------------------------------------------------------ 5 | # 6 | # RFXSEND.PY 7 | # 8 | # Copyright (C) 2012-2013 Sebastian Sjoholm, sebastian.sjoholm@gmail.com 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program 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 General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | # 23 | # Website: http://code.google.com/p/rfxcmd/ 24 | # 25 | # $Rev: 566 $ 26 | # $Date: 2013-11-22 21:43:41 +0100 (Fri, 22 Nov 2013) $ 27 | # 28 | # NOTES 29 | # 30 | # RFXCOM is a Trademark of RFSmartLink. 31 | # 32 | # ------------------------------------------------------------------------------ 33 | # 34 | # Protocol License Agreement 35 | # 36 | # The RFXtrx protocols are owned by RFXCOM, and are protected under applicable 37 | # copyright laws. 38 | # 39 | # ============================================================================== 40 | # It is only allowed to use this protocol or any part of it for RFXCOM products 41 | # ============================================================================== 42 | # 43 | # The above Protocol License Agreement and the permission notice shall be 44 | # included in all software using the RFXtrx protocols. 45 | # 46 | # Any use in violation of the foregoing restrictions may subject the user to 47 | # criminal sanctions under applicable laws, as well as to civil liability for 48 | # the breach of the terms and conditions of this license. 49 | # 50 | # ------------------------------------------------------------------------------ 51 | 52 | __author__ = "Sebastian Sjoholm" 53 | __copyright__ = "Copyright 2012-2013, Sebastian Sjoholm" 54 | __license__ = "GPL" 55 | __version__ = "0.1 (" + filter(str.isdigit, "$Rev: 566 $") + ")" 56 | __maintainer__ = "Sebastian Sjoholm" 57 | __email__ = "sebastian.sjoholm@gmail.com" 58 | __status__ = "Development" 59 | __date__ = "$Date: 2013-11-22 21:43:41 +0100 (Fri, 22 Nov 2013) $" 60 | 61 | # Default modules 62 | import sys 63 | import string 64 | import socket 65 | import optparse 66 | 67 | # ---------------------------------------------------------------------------- 68 | 69 | def ByteToHex( byteStr ): 70 | """ 71 | Convert a byte string to it's hex string representation e.g. for output. 72 | http://code.activestate.com/recipes/510399-byte-to-hex-and-hex-to-byte-string-conversion/ 73 | 74 | Added str() to byteStr in case input data is in integer 75 | """ 76 | return ''.join( [ "%02X " % ord( x ) for x in str(byteStr) ] ).strip() 77 | 78 | # ---------------------------------------------------------------------------- 79 | 80 | def stripped(str): 81 | """ 82 | Strip all characters that are not valid 83 | Credit: http://rosettacode.org/wiki/Strip_control_codes_and_extended_characters_from_a_string 84 | """ 85 | return "".join([i for i in str if ord(i) in range(32, 127)]) 86 | 87 | # ----------------------------------------------------------------------------- 88 | 89 | def print_version(): 90 | """ 91 | Print RFXSEND version, build and date 92 | """ 93 | print "RFXSEND Version: " + __version__ 94 | print __date__.replace('$', '') 95 | sys.exit(0) 96 | 97 | # ----------------------------------------------------------------------------- 98 | 99 | def test_message( message ): 100 | """ 101 | Test, filter and verify that the incoming message is valid 102 | Return true if valid, False if not 103 | """ 104 | 105 | # Remove any whitespaces and linebreaks 106 | message = message.replace(' ', '') 107 | message = message.replace("\r","") 108 | message = message.replace("\n","") 109 | 110 | # Remove all invalid characters 111 | message = stripped(message) 112 | 113 | # Test the string if it is hex format 114 | try: 115 | int(message,16) 116 | except Exception: 117 | return False 118 | 119 | # Check that length is even 120 | if len(message) % 2: 121 | return False 122 | 123 | # Check that first byte is not 00 124 | if ByteToHex(message.decode('hex')[0]) == "00": 125 | return False 126 | 127 | # Length more than one byte 128 | if not len(message.decode('hex')) > 1: 129 | return False 130 | 131 | # Check if string is the length that it reports to be 132 | cmd_len = int( ByteToHex( message.decode('hex')[0]),16 ) 133 | if not len(message.decode('hex')) == (cmd_len + 1): 134 | return False 135 | 136 | return True 137 | 138 | # ----------------------------------------------------------------------------- 139 | 140 | def send_message(socket_server, socket_port, message): 141 | """ 142 | 143 | Send message to the RFXCMD socket server 144 | 145 | Input: 146 | - socket_server = IP address at RFXCMD 147 | - socket_port = socket port at RFXCMD 148 | - message = raw RFX message to be sent 149 | 150 | Output: None 151 | 152 | """ 153 | sock = None 154 | 155 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 156 | sock.connect((socket_server, socket_port)) 157 | sock.send(message) 158 | sock.close() 159 | 160 | # ----------------------------------------------------------------------------- 161 | 162 | if __name__ == '__main__': 163 | 164 | parser = optparse.OptionParser() 165 | parser.add_option("-s", "--server", action="store", type="string", dest="server", help="IP address of the RFXCMD server (default: localhost)") 166 | parser.add_option("-p", "--port", action="store", type="string", dest="port", help="Port of the RFXCMD server (default: 55000)") 167 | parser.add_option("-r", "--rawcmd", action="store", type="string", dest="rawcmd", help="The raw message to be sent, multiple messages separated with comma") 168 | parser.add_option("-i", "--simulate", action="store_true", dest="simulate", help="Simulate send, nothing will be sent, instead printed on STDOUT") 169 | parser.add_option("-v", "--version", action="store_true", dest="version", help="Print rfxcmd version information") 170 | 171 | (options, args) = parser.parse_args() 172 | 173 | if options.version: 174 | print_version() 175 | 176 | if options.server: 177 | socket_server = options.server 178 | else: 179 | socket_server = 'localhost' 180 | 181 | if options.port: 182 | socket_port = int(options.port) 183 | else: 184 | socket_port = 55000 185 | 186 | if options.simulate: 187 | simulate = True 188 | else: 189 | simulate = False 190 | 191 | if options.rawcmd: 192 | message = options.rawcmd 193 | 194 | # check for multiple messages 195 | buf = message.split(',') 196 | 197 | else: 198 | print "Error: rawcmd message is missing" 199 | sys.exit(1) 200 | 201 | for msg in buf: 202 | if test_message(msg): 203 | try: 204 | if simulate == False: 205 | send_message(socket_server, socket_port, msg) 206 | else: 207 | print("Message to send, Server: " + str(socket_server) + ":" + str(socket_port) + ", Message: " + msg); 208 | except socket.error as err: 209 | print "Error: Could not send message: %s " % err 210 | else: 211 | print "Command not sent, invalid format" 212 | 213 | sys.exit(0) 214 | 215 | # ------------------------------------------------------------------------------ 216 | # END 217 | # ------------------------------------------------------------------------------ 218 | -------------------------------------------------------------------------------- /trigger.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0D0..000025332000E6F01000000 4 | echo TEST 5 | 6 | 7 | 0A5201................ 8 | echo "I'm alive" 9 | 10 | 11 | 0A5201..8E02.......... 12 | echo Temperature $temperature$ 13 | 14 | 15 | -------------------------------------------------------------------------------- /weewx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5202 4 | 1500 5 | 6 | 7 | 5402 8 | 1E00 9 | 10 | 11 | 5402 12 | 2301 13 | 14 | 15 | -------------------------------------------------------------------------------- /weewx/rfxcmd_weewx.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012 Tom Keffer + m. bakker 3 | # 4 | # See the file LICENSE.txt for your full rights. 5 | # 6 | # $Revision: 1000 7 | # $Author: tkeffer + mbakker 8 | # 9 | """Console RfxCmd for the weewx weather system""" 10 | 11 | from __future__ import with_statement 12 | import math 13 | import time 14 | import socket 15 | import syslog 16 | from datetime import datetime 17 | 18 | import weedb 19 | import weeutil.weeutil 20 | import weewx.abstractstation 21 | import weewx.wxformulas 22 | 23 | # General decoding sensor maps. 24 | WIND_DIR_MAP = { 0:'N', 1:'NNE', 2:'NE', 3:'ENE', 25 | 4:'E', 5:'ESE', 6:'SE', 7:'SSE', 26 | 8:'S', 9:'SSW', 10:'SW', 11:'WSW', 27 | 12:'W', 13:'WNW', 14:'NW', 15:'NNW' } 28 | FORECAST_MAP = { 0:'Partly Cloudy', 1:'Rainy', 2:'Cloudy', 3:'Sunny', 29 | 4:'Clear Night', 5:'Snowy', 30 | 6:'Partly Cloudy Night', 7:'Unknown7' } 31 | HUMIDITY_STATUS = { "00":"Dry", "01":"Comfort", "02":"Normal", "03":"Wet"} 32 | TRENDS = { 0:'Stable', 1:'Rising', 2:'Falling', 3:'Undefined' } 33 | 34 | def loader(config_dict, engine): 35 | """Used to load the driver.""" 36 | # The WMR driver needs the altitude in meters. Get it from the Station data 37 | # and do any necessary conversions. 38 | altitude_t = weeutil.weeutil.option_as_list( config_dict['Station'].get('altitude', (None, None))) 39 | # Form a value-tuple 40 | altitude_vt = (float(altitude_t[0]), altitude_t[1], 'group_altitude') 41 | # Now convert to meters, using only the first element of the returned 42 | # value-tuple. 43 | altitude_m = weewx.units.convert(altitude_vt, 'meter')[0] 44 | 45 | station = RfxCmd(altitude=altitude_m, **config_dict['RfxCmd']) 46 | 47 | return station 48 | 49 | class RfxCmd(weewx.abstractstation.AbstractStation): 50 | 51 | def to_bool(self, value): 52 | if str(value).lower() in ("yes", "y", "true", "t", "1"): 53 | return True 54 | if str(value).lower() in ("no", "n", "false", "f", "0", "0.0", "", "none", "[]", "{}"): 55 | return False 56 | raise Exception('Invalid value for boolean conversion: ' + str(value)) 57 | 58 | def writelog(self, string): 59 | if self.rfx_logfile and self.rfx_debug: 60 | try: 61 | timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 62 | f = open(self.rfx_logfile,'a+') 63 | f.write(timestamp + " - " + string + "\n") 64 | f.close() 65 | except Exception as err: 66 | syslog.syslog(syslog.LOG_ERR, "RfxCmd: Could not write to logfile" % (str(self.rfx_logfile))) 67 | syslog.syslog(syslog.LOG_ERR, "RfxCmd: Exception - " % (str(err))) 68 | pass 69 | 70 | def __init__(self, **stn_dict): 71 | 72 | """Initialize """ 73 | # Debug 74 | self.rfx_debug = self.to_bool(stn_dict.get('debug', False)) 75 | self.rfx_logfile = stn_dict.get('logfile', None) 76 | 77 | self.writelog("----------------------------------") 78 | self.writelog("Init RFXcmd sensor data collection") 79 | 80 | self.altitude = stn_dict['altitude'] 81 | self.writelog("Altitude = %s" % str(self.altitude)) 82 | self.mode = stn_dict['mode'] 83 | self.writelog("Mode = %s" % str(self.mode)) 84 | 85 | self.last_totalRain = None 86 | 87 | # RFXcmd Socket data 88 | self.socket_server = stn_dict.get('socket_server', 'localhost') 89 | self.writelog("Socket server = %s" % str(self.socket_server)) 90 | self.socket_port = int(stn_dict.get('socket_port', 55000)) 91 | self.writelog("Socket port = %s" % str(self.socket_port)) 92 | self.socket_message = stn_dict.get('socket_message', '0A1100FF001100FF001100') 93 | 94 | # Sensor 95 | self.legacy = self.to_bool(stn_dict.get('legacy', False)) 96 | 97 | # 0x4F Temp and Rain sensors 98 | self.sensor_0x4F = self.to_bool(stn_dict.get('sensor_0x4F', False)) 99 | self.writelog("Sensor 0x4f = %s" % str(self.sensor_0x4F)) 100 | self.sensor_0x4F_temp = stn_dict.get('sensor_0x4F_temp', False) 101 | self.sensor_0x4F_raintotal = stn_dict.get('sensor_0x4F_raintotal', False) 102 | self.sensor_0x4F_batt = stn_dict.get('sensor_0x4F_batt', False) 103 | self.sensor_0x4F_rssi = stn_dict.get('sensor_0x4F_rssi', False) 104 | 105 | # 0x50 Temperature Sensors 106 | self.sensor_0x50 = self.to_bool(stn_dict.get('sensor_0x50', False)) 107 | self.writelog("Sensor 0x50 = %s" % str(self.sensor_0x50)) 108 | self.sensor_0x50_temp = stn_dict.get('sensor_0x50_temp', False) 109 | self.sensor_0x50_batt = stn_dict.get('sensor_0x50_batt', False) 110 | self.sensor_0x50_rssi = stn_dict.get('sensor_0x50_rssi', False) 111 | 112 | # 0x51 Humidity Sensors 113 | self.sensor_0x51 = self.to_bool(stn_dict.get('sensor_0x51', False)) 114 | self.writelog("Sensor 0x51 = %s" % str(self.sensor_0x51)) 115 | self.sensor_0x51_hum = stn_dict.get('sensor_0x51_hum', False) 116 | self.sensor_0x51_batt = stn_dict.get('sensor_0x51_batt', False) 117 | self.sensor_0x51_rssi = stn_dict.get('sensor_0x51_rssi', False) 118 | 119 | # 0x52 Temp/Baro Sensors 120 | self.sensor_0x52 = self.to_bool(stn_dict.get('sensor_0x52', False)) 121 | self.writelog("Sensor 0x52 = %s" % str(self.sensor_0x52)) 122 | self.sensor_0x52_temp = stn_dict.get('sensor_0x52_temp', False) 123 | self.sensor_0x52_hum = stn_dict.get('sensor_0x52_hum', False) 124 | self.sensor_0x52_batt = stn_dict.get('sensor_0x52_batt', False) 125 | self.sensor_0x52_rssi = stn_dict.get('sensor_0x52_rssi', False) 126 | 127 | # 0x53 Barometric Sensors 128 | self.sensor_0x53 = self.to_bool(stn_dict.get('sensor_0x53', False)) 129 | self.writelog("Sensor 0x53 = %s" % str(self.sensor_0x53)) 130 | self.sensor_0x53_baro = stn_dict.get('sensor_0x53_baro', False) 131 | self.sensor_0x53_batt = stn_dict.get('sensor_0x53_batt', False) 132 | self.sensor_0x53_rssi = stn_dict.get('sensor_0x53_rssi', False) 133 | 134 | # 0x54 Temp/Hum/Baro Sensors 135 | self.sensor_0x54 = self.to_bool(stn_dict.get('sensor_0x54', False)) 136 | self.writelog("Sensor 0x54 = %s" % str(self.sensor_0x54)) 137 | self.sensor_0x54_temp = stn_dict.get('sensor_0x54_temp', False) 138 | self.sensor_0x54_hum = stn_dict.get('sensor_0x54_hum', False) 139 | self.sensor_0x54_baro = stn_dict.get('sensor_0x54_baro', False) 140 | self.sensor_0x54_batt = stn_dict.get('sensor_0x54_batt', False) 141 | self.sensor_0x54_rssi = stn_dict.get('sensor_0x54_rssi', False) 142 | 143 | # 0x55 Rain Sensors 144 | self.sensor_0x55 = self.to_bool(stn_dict.get('sensor_0x55', False)) 145 | self.writelog("Sensor 0x55 = %s" % str(self.sensor_0x55)) 146 | self.sensor_0x55_rainrate = stn_dict.get('sensor_0x55_rainrate', False) 147 | self.sensor_0x55_raintotal = stn_dict.get('sensor_0x55_raintotal', False) 148 | self.sensor_0x55_batt = stn_dict.get('sensor_0x55_batt', False) 149 | self.sensor_0x55_rssi = stn_dict.get('sensor_0x55_rssi', False) 150 | 151 | # 0x56 Wind Sensors 152 | self.sensor_0x56 = self.to_bool(stn_dict.get('sensor_0x56', False)) 153 | self.writelog("Sensor 0x56 = %s" % str(self.sensor_0x56)) 154 | self.sensor_0x56_direction = stn_dict.get('sensor_0x56_direction', False) 155 | self.sensor_0x56_avspeed = stn_dict.get('sensor_0x56_avspeed', False) 156 | self.sensor_0x56_gust = stn_dict.get('sensor_0x56_gust', False) 157 | self.sensor_0x56_temp = stn_dict.get('sensor_0x56_temp', False) 158 | self.sensor_0x56_chill = stn_dict.get('sensor_0x56_chill', False) 159 | self.sensor_0x56_batt = stn_dict.get('sensor_0x56_batt', False) 160 | self.sensor_0x56_rssi = stn_dict.get('sensor_0x56_rssi', False) 161 | 162 | # Sensor 0x57 UV 163 | self.sensor_0x57 = self.to_bool(stn_dict.get('sensor_0x57', False)) 164 | self.writelog("Sensor 0x57 = %s" % str(self.sensor_0x57)) 165 | self.sensor_0x57_uv = stn_dict.get('sensor_0x57_uv', False) 166 | self.sensor_0x57_temp = stn_dict.get('sensor_0x57_temp', False) 167 | self.sensor_0x57_batt = stn_dict.get('sensor_0x57_batt', False) 168 | self.sensor_0x57_rssi = stn_dict.get('sensor_0x57_rssi', False) 169 | 170 | self.stale_wind = int(stn_dict.get('stale_wind', 30)) 171 | self.writelog("Stale wind = %s " % str(self.stale_wind)) 172 | self.loop_interval = float(stn_dict.get('loop_interval', 60)) 173 | self.writelog("Loop interval = %s " % str(self.loop_interval)) 174 | start_ts = self.the_time = time.time() 175 | self.writelog("Init done") 176 | 177 | def get_rfxdata(self, sensor): 178 | syslog.syslog(syslog.LOG_ERR, "RfxCmd: Get data") 179 | 180 | data = None 181 | rbufsize = -1 182 | wbufsize = 0 183 | packet_recv = False 184 | sock = None 185 | 186 | self.writelog("Get data for sensor %s " % str(sensor)) 187 | message = "WEEWX;" + sensor 188 | self.writelog("Remote: %s:%s" % (str(self.socket_server), str(self.socket_port))) 189 | self.writelog("Open socket connection") 190 | try: 191 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 192 | sock.connect((self.socket_server, self.socket_port)) 193 | self.writelog("Socket OK") 194 | except Exception as err: 195 | self.writelog("Exception: %s " % str(err)) 196 | syslog.syslog(syslog.LOG_ERR, "RfxCmd: Exception: %s " % str(err)) 197 | data = None 198 | sock = None 199 | pass 200 | 201 | if sock: 202 | try: 203 | self.writelog("Send socket command = %s" % str(message)) 204 | rfile = sock.makefile('rb', rbufsize) 205 | wfile = sock.makefile('wb', wbufsize) 206 | wfile.write( message +"\n" ) 207 | data = rfile.read() 208 | self.writelog("Received data: %s " % str(data)) 209 | self.writelog("Close socket") 210 | sock.close() 211 | except socket.error as err: 212 | data = None 213 | self.writelog("Error: Failed to connect to remote") 214 | self.writelog("Exception: %s " % str(err)) 215 | syslog.syslog(syslog.LOG_ERR, "RfxCmd: Exception: %s " % str(err)) 216 | pass 217 | 218 | if data: 219 | return data 220 | else: 221 | return None 222 | 223 | def genLoopPackets(self): 224 | while True: 225 | # Determine how long to sleep 226 | # We are in real time mode. Try to keep synched up with the 227 | # wall clock 228 | sleep_time = self.the_time + self.loop_interval - time.time() 229 | if sleep_time > 0: 230 | time.sleep(sleep_time) 231 | 232 | # Update the simulator clock: 233 | self.the_time += self.loop_interval 234 | packet_recv = 0 235 | 236 | # -------------------------------------------------------------------------------------- 237 | # Sensor 0x52 238 | # -------------------------------------------------------------------------------------- 239 | if bool(self.sensor_0x52): 240 | self.writelog("0x52: --- Start ---") 241 | result = None 242 | _packet = {'dateTime': int(self.the_time+0.5), 'usUnits' : weewx.METRIC } 243 | result = self.get_rfxdata("0x52") 244 | self.writelog("0x52: Data: %s " % str(result)) 245 | if result: 246 | data = result.split(';') 247 | if not bool(self.sensor_0x52_temp) == False: 248 | self.writelog("0x52: %s = %s" % (str(self.sensor_0x52_temp), (str(data[0])))) 249 | _packet[self.sensor_0x52_temp] = float(data[0]) 250 | 251 | if not bool(self.sensor_0x52_hum) == False: 252 | self.writelog("0x52: %s = %s" % (str(self.sensor_0x52_hum), (str(data[1])))) 253 | _packet[self.sensor_0x52_hum] = float(data[1]) 254 | 255 | if not bool(self.sensor_0x52_batt) == False: 256 | self.writelog("0x52: %s = %s" % (str(self.sensor_0x52_batt), (str(data[2])))) 257 | _packet[self.sensor_0x52_batt] = float(data[2]) 258 | 259 | if not bool(self.sensor_0x52_rssi) == False: 260 | self.writelog("0x52: %s = %s" % (str(self.sensor_0x52_rssi), (str(data[3])))) 261 | _packet[self.sensor_0x52_rssi] = float(data[3]) 262 | 263 | _packet['dewpoint'] = weewx.wxformulas.dewpointC(_packet[self.sensor_0x52_temp], _packet[self.sensor_0x52_hum]) 264 | _packet['heatindex'] = weewx.wxformulas.heatindexC(_packet[self.sensor_0x52_temp], _packet[self.sensor_0x52_hum]) 265 | OT = _packet[self.sensor_0x52_temp] 266 | 267 | yield _packet 268 | self.writelog("0x52: --- End ---") 269 | else: 270 | self.writelog("0x52: Result None, no process") 271 | 272 | # -------------------------------------------------------------------------------------- 273 | # Sensor 0x53 274 | # -------------------------------------------------------------------------------------- 275 | if bool(self.sensor_0x53): 276 | self.writelog("0x53: --- Start ---") 277 | result = None 278 | _packet = {'dateTime': int(self.the_time+0.5), 'usUnits' : weewx.METRIC } 279 | result = self.get_rfxdata("0x53") 280 | self.writelog("0x53: Data: %s " % str(result)) 281 | if result: 282 | data = result.split(';') 283 | if not bool(self.sensor_0x53_baro) == False: 284 | self.writelog("0x53: %s = %s" % (str(self.sensor_0x53_hum), (str(data[1])))) 285 | _packet[self.sensor_0x53_baro] = float(data[1]) 286 | 287 | if not bool(self.sensor_0x53_batt) == False: 288 | self.writelog("0x53: %s = %s" % (str(self.sensor_0x53_batt), (str(data[2])))) 289 | _packet[self.sensor_0x53_batt] = float(data[2]) 290 | 291 | if not bool(self.sensor_0x53_rssi) == False: 292 | self.writelog("0x53: %s = %s" % (str(self.sensor_0x53_rssi), (str(data[3])))) 293 | _packet[self.sensor_0x53_rssi] = float(data[3]) 294 | 295 | yield _packet 296 | self.writelog("0x53: --- End ---") 297 | else: 298 | self.writelog("0x53: Result None, no process") 299 | 300 | # -------------------------------------------------------------------------------------- 301 | # Sensor 0x54 302 | # -------------------------------------------------------------------------------------- 303 | if bool(self.sensor_0x54): 304 | self.writelog("0x54: --- Start ---") 305 | result = None 306 | _packet = {'dateTime': int(self.the_time+0.5), 'usUnits' : weewx.METRIC } 307 | result = self.get_rfxdata("0x54") 308 | self.writelog("0x54: Data: %s " % str(result)) 309 | if result: 310 | data = result.split(';') 311 | if not bool(self.sensor_0x54_temp) == False: 312 | self.writelog("0x54: %s = %s" % (str(self.sensor_0x54_temp), (str(data[0])))) 313 | _packet[self.sensor_0x54_temp] = float(data[0]) 314 | 315 | if not bool(self.sensor_0x54_hum) == False: 316 | self.writelog("0x54: %s = %s" % (str(self.sensor_0x54_hum), (str(data[1])))) 317 | _packet[self.sensor_0x54_hum] = float(data[1]) 318 | 319 | if not bool(self.sensor_0x54_baro) == False: 320 | self.writelog("0x54: %s = %s" % (str(self.sensor_0x54_baro), (str(data[1])))) 321 | _packet[self.sensor_0x54_baro] = float(data[2]) 322 | 323 | if not bool(self.sensor_0x54_batt) == False: 324 | self.writelog("0x54: %s = %s" % (str(self.sensor_0x54_batt), (str(data[2])))) 325 | _packet[self.sensor_0x54_batt] = float(data[3]) 326 | 327 | if not bool(self.sensor_0x54_rssi) == False: 328 | self.writelog("0x54: %s = %s" % (str(self.sensor_0x54_rssi), (str(data[3])))) 329 | _packet[self.sensor_0x54_rssi] = float(data[4]) 330 | 331 | SA = weewx.wxformulas.altimeter_pressure_Metric(_packet[self.sensor_0x54_baro], self.altitude) 332 | _packet['altimeter'] = SA 333 | 334 | yield _packet 335 | self.writelog("0x54: --- End ---") 336 | else: 337 | self.writelog("0x54: Result None, no process") 338 | 339 | # -------------------------------------------------------------------------------------- 340 | # Sensor 0x55 341 | # -------------------------------------------------------------------------------------- 342 | if bool(self.sensor_0x55): 343 | self.writelog("0x55: --- Start ---") 344 | result = None 345 | _packet = {'dateTime': int(self.the_time+0.5), 'usUnits' : weewx.METRIC } 346 | result = self.get_rfxdata("0x55") 347 | self.writelog("0x55: Data: %s " % str(result)) 348 | if result: 349 | data = result.split(';') 350 | if not bool(self.sensor_0x55_rainrate) == False: 351 | self.writelog("0x55: %s = %s" % (str(self.sensor_0x55_rainrate), (str(data[0])))) 352 | _packet[self.sensor_0x55_rainrate] = float(data[0]) 353 | 354 | if not bool(self.sensor_0x55_raintotal) == False: 355 | self.writelog("0x55: %s = %s" % (str(self.sensor_0x55_raintotal), (str(data[1])))) 356 | _packet[self.sensor_0x55_raintotal] = float(data[1]) 357 | 358 | if not bool(self.sensor_0x55_batt) == False: 359 | self.writelog("0x55: %s = %s" % (str(self.sensor_0x55_batt), (str(data[2])))) 360 | _packet[self.sensor_0x55_batt] = float(data[2]) 361 | 362 | if not bool(self.sensor_0x55_rssi) == False: 363 | self.writelog("0x55: %s = %s" % (str(self.sensor_0x55_rssi), (str(data[3])))) 364 | _packet[self.sensor_0x55_rssi] = float(data[3]) 365 | 366 | _packet['rain'] = ((_packet[self.sensor_0x55_raintotal]-self.last_totalRain)/10) if self.last_totalRain is not None else None 367 | 368 | if _packet['rain'] == _packet[self.sensor_0x55_raintotal]: 369 | _packet['rain'] = None 370 | 371 | self.last_totalRain = _packet[self.sensor_0x55_raintotal] 372 | 373 | yield _packet 374 | self.writelog("0x55: --- End ---") 375 | else: 376 | self.writelog("0x55: Result None, no process") 377 | 378 | # -------------------------------------------------------------------------------------- 379 | # Sensor 0x56 380 | # -------------------------------------------------------------------------------------- 381 | if bool(self.sensor_0x56): 382 | self.writelog("0x56: --- Start ---") 383 | result = None 384 | _packet = {'dateTime': int(self.the_time+0.5), 'usUnits' : weewx.METRICWX } 385 | result = self.get_rfxdata("0x56") 386 | self.writelog("0x56: Data: %s " % str(result)) 387 | if result: 388 | data = result.split(';') 389 | if not bool(self.sensor_0x56_direction) == False: 390 | self.writelog("0x56: %s = %s" % (str(self.sensor_0x56_direction), (str(data[0])))) 391 | _packet[self.sensor_0x56_direction] = float(data[0]) 392 | 393 | if not bool(self.sensor_0x56_avspeed) == False: 394 | self.writelog("0x56: %s = %s" % (str(self.sensor_0x56_avspeed), (str(data[1])))) 395 | _packet[self.sensor_0x56_avspeed] = float(data[1]) 396 | 397 | if not bool(self.sensor_0x56_temp) == False: 398 | if not data[2] == "None": 399 | self.writelog("0x56: %s = %s" % (str(self.sensor_0x56_temp), (str(data[2])))) 400 | _packet[self.sensor_0x56_temp] = float(data[2]) 401 | 402 | if not bool(self.sensor_0x56_gust) == False: 403 | self.writelog("0x56: %s = %s" % (str(self.sensor_0x56_gust), (str(data[3])))) 404 | _packet[self.sensor_0x56_gust] = float(data[3]) 405 | 406 | if not bool(self.sensor_0x56_chill) == False: 407 | if not data[4] == "None": 408 | self.writelog("0x56: %s = %s" % (str(self.sensor_0x56_chill), (str(data[4])))) 409 | _packet[self.sensor_0x56_chill] = float(data[4]) 410 | 411 | if not bool(self.sensor_0x56_batt) == False: 412 | self.writelog("0x56: %s = %s" % (str(self.sensor_0x56_batt), (str(data[5])))) 413 | _packet[self.sensor_0x56_batt] = float(data[5]) 414 | 415 | if not bool(self.sensor_0x56_rssi) == False: 416 | self.writelog("0x56: %s = %s" % (str(self.sensor_0x56_rssi), (str(data[6])))) 417 | _packet[self.sensor_0x56_rssi] = float(data[6]) 418 | 419 | 420 | if _packet[self.sensor_0x56_avspeed] == 0: 421 | _packet[self.sensor_0x56_direction] = None 422 | 423 | _packet['windchill'] = weewx.wxformulas.windchillC(OT, _packet[self.sensor_0x56_avspeed]) 424 | 425 | yield _packet 426 | self.writelog("0x56: --- End ---") 427 | else: 428 | self.writelog("0x56: Result None, no process") 429 | 430 | # -------------------------------------------------------------------------------------- 431 | # Sensor 0x57 UV 432 | # -------------------------------------------------------------------------------------- 433 | if bool(self.sensor_0x57): 434 | self.writelog("0x57: --- Start ---") 435 | result = None 436 | _packet = {'dateTime': int(self.the_time+0.5), 'usUnits' : weewx.METRIC } 437 | result = self.get_rfxdata("0x57") 438 | self.writelog("0x57: Data: %s " % str(result)) 439 | if result: 440 | data = result.split(';') 441 | if not bool(self.sensor_0x57_uv) == False: 442 | self.writelog("0x57: %s = %s" % (str(self.sensor_0x57_uv), (str(data[0])))) 443 | _packet[self.sensor_0x57_uv] = float(data[0]) 444 | 445 | if not bool(self.sensor_0x57_temp) == False: 446 | if not data[1] == "None": 447 | self.writelog("0x57: %s = %s" % (str(self.sensor_0x57_temp), (str(data[1])))) 448 | _packet[self.sensor_0x57_temp] = float(data[1]) 449 | 450 | if not bool(self.sensor_0x57_batt) == False: 451 | self.writelog("0x57: %s = %s" % (str(self.sensor_0x57_batt), (str(data[2])))) 452 | _packet[self.sensor_0x57_batt] = float(data[2]) 453 | 454 | if not bool(self.sensor_0x57_rssi) == False: 455 | self.writelog("0x57: %s = %s" % (str(self.sensor_0x57_rssi), (str(data[3])))) 456 | _packet[self.sensor_0x57_rssi] = float(data[3]) 457 | 458 | yield _packet 459 | self.writelog("0x57: --- End ---") 460 | else: 461 | self.writelog("0x57: Result None, no process") 462 | 463 | ''' 464 | # Update weather_data: 465 | if bool(self.legacy) == True: 466 | try: 467 | syslog.syslog(syslog.LOG_ERR, "rfxcmd: Get data") 468 | data= '' 469 | rbufsize= -1 470 | wbufsize= 0 471 | sock = None 472 | syslog.syslog(syslog.LOG_ERR, "rfxcmd: Server = %s, Port = %s " % (str(self.socket_server), str(self.socket_port))) 473 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 474 | sock.connect((self.socket_server, self.socket_port)) 475 | rfile = sock.makefile('rb', rbufsize) 476 | wfile = sock.makefile('wb', wbufsize) 477 | wfile.write( self.socket_message +"\n" ) 478 | syslog.syslog(syslog.LOG_ERR, "rfxcmd: Socket message: %s " % str(self.socket_message)) 479 | data= rfile.read() 480 | sock.close() 481 | sd = data.split("|") 482 | packet_recv = 1 483 | except socket.error as err: 484 | syslog.syslog(syslog.LOG_ERR, "rfxcmd: Failed to connect!") 485 | syslog.syslog(syslog.LOG_ERR, "rfxcmd: Error: %s " % str(err)) 486 | pass 487 | 488 | if packet_recv == 1: 489 | syslog.syslog(syslog.LOG_ERR, "rfxcmd: Received %s " % str(sd)) 490 | _packet = {'dateTime': int(self.the_time+0.5), 'usUnits' : weewx.METRIC } 491 | 492 | if sd[3] != '0': 493 | _packet['windSpeed'] = float(sd[5]) 494 | _packet['windDir'] = float(sd[4]) 495 | _packet['windGust'] = float(sd[6]) 496 | 497 | if sd[11] != '0': 498 | _packet['outTemp'] = float(sd[12]) 499 | _packet['outHumidity'] = float(sd[13]) 500 | _packet['dewpoint'] = weewx.wxformulas.dewpointC(_packet['outTemp'], _packet['outHumidity']) 501 | _packet['heatindex'] = weewx.wxformulas.heatindexC(_packet['outTemp'], _packet['outHumidity']) 502 | 503 | if sd[19] != '0': 504 | _packet['inTemp'] = float(sd[20]) 505 | _packet['inHumidity'] = float(sd[21]) 506 | _packet['pressure'] = float(sd[23]) 507 | SA = weewx.wxformulas.altimeter_pressure_Metric(_packet['pressure'], self.altitude) 508 | _packet['altimeter'] = SA 509 | 510 | if sd[29] != '0': 511 | _packet['rain'] = float(sd[30]) 512 | _packet['UV'] = float(sd[40]) 513 | 514 | if sd[3] != '0' and sd[11] != '0': 515 | _packet['windchill'] = weewx.wxformulas.windchillC(_packet['outTemp'], _packet['windSpeed']) 516 | 517 | _packet['inTempBatteryStatus'] = 1.0 518 | _packet['OutTempBatteryStatus'] = 1.0 519 | _packet['rainBatteryStatus'] = 1.0 520 | _packet['windBatteryStatus'] = 1.0 521 | _packet['txBatteryStatus'] = 1.0 522 | _packet['rxCheckPercent'] = 1.0 523 | 524 | if float(sd[7]) < 2: 525 | _packet['windBatteryStatus'] = 0 526 | 527 | if float(sd[15]) < 2: 528 | _packet['OutTempBatteryStatus'] = 0 529 | 530 | if float(sd[25]) < 2: 531 | _packet['InTempBatteryStatus'] = 0 532 | 533 | if float(sd[35]) < 2: 534 | _packet['rainBatteryStatus'] = 0 535 | 536 | yield _packet 537 | syslog.syslog(syslog.LOG_NOTICE, "rfxcmd: stored new data.") 538 | ''' 539 | 540 | def getTime(self): 541 | return self.the_time 542 | 543 | @property 544 | def hardware_name(self): 545 | return "RfxCmd" 546 | 547 | if __name__ == "__main__": 548 | station = RfxCmd(mode='simulator',loop_interval=2.0) 549 | for packet in station.genLoopPackets(): 550 | print weeutil.weeutil.timestamp_to_string(packet['dateTime']), packet 551 | -------------------------------------------------------------------------------- /weewx/rfxcmd_weewx_config.txt: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | 3 | [RfxCmd] 4 | 5 | # This section is for the RFXCMD 6 | 7 | # The model is RFXCMD 8 | model = RfxCmd 9 | 10 | # Do not know 11 | mode = socket 12 | 13 | # RFXcmd Socket server 14 | socket_server = localhost 15 | socket_port = 55000 16 | 17 | # Debug 18 | debug = True 19 | logfile = "/tmp/weewx_rfxcmd.log" 20 | 21 | # RFX Sensors 22 | sensor_0x4F = False 23 | sensor_0x50 = False 24 | sensor_0x51 = False 25 | sensor_0x52 = False 26 | sensor_0x53 = False 27 | sensor_0x54 = False 28 | sensor_0x55 = False 29 | sensor_0x56 = False 30 | sensor_0x57 = False 31 | 32 | # 0x4F Temp and Rain sensors 33 | sensor_0x4F_temp = "outTemp" 34 | sensor_0x4F_raintotal = "totalRain" 35 | sensor_0x4F_batt = "" 36 | sensor_0x4F_rssi = "" 37 | 38 | # 0x50 Temperature Sensors 39 | sensor_0x50_temp = "inTemp" 40 | sensor_0x50_batt = "" 41 | sensor_0x50_rssi = "" 42 | 43 | # 0x51 Humidity Sensors 44 | sensor_0x51_hum = "outHumidity" 45 | sensor_0x51_batt = "" 46 | sensor_0x51_rssi = "" 47 | 48 | # 0x52 Temp/Baro Sensors 49 | sensor_0x52_temp = "outTemp" 50 | sensor_0x52_hum = "outHumidity" 51 | sensor_0x52_batt = "" 52 | sensor_0x52_rssi = "" 53 | 54 | # 0x53 Barometric Sensors 55 | sensor_0x53_baro = "barometer" 56 | sensor_0x53_batt = "" 57 | sensor_0x53_rssi = "" 58 | 59 | # 0x54 Temp/Hum/Baro Sensors 60 | sensor_0x54_temp = "outTemp" 61 | sensor_0x54_hum = "outHumidity" 62 | sensor_0x54_baro = "barometer" 63 | sensor_0x54_batt = "" 64 | sensor_0x54_rssi = "" 65 | 66 | # 0x55 Rain Sensors 67 | sensor_0x55_rainrate = "rain" 68 | sensor_0x55_raintotal = "totalRain" 69 | sensor_0x55_batt = "" 70 | sensor_0x55_rssi = "" 71 | 72 | # 0x56 Wind Sensors 73 | sensor_0x56_direction = "windDir" 74 | sensor_0x56_avspeed = "windSpeed" 75 | sensor_0x56_gust = "windGust" 76 | sensor_0x56_temp = "outTemp" 77 | sensor_0x56_chill = "" 78 | sensor_0x56_batt = "" 79 | sensor_0x56_rssi = "" 80 | 81 | # 0x57 UV Sensors 82 | sensor_0x57_uv = "UV" 83 | sensor_0x57_temp = "extraTemp1" 84 | sensor_0x57_batt = "" 85 | sensor_0x57_rssi = "" 86 | 87 | # The driver to use 88 | driver = weewx.drivers.rfxcmd_weewx 89 | 90 | ############################################################################## 91 | -------------------------------------------------------------------------------- /whitelist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0D01........................ 4 | 0A5201................ 5 | 6 | 7 | --------------------------------------------------------------------------------