├── .gitignore ├── LICENSE ├── Makefile ├── OpenWrt └── Makefile ├── README.md ├── alsa.c ├── auchan.c ├── auchan2.c ├── blyss.c ├── dio.c ├── dummy.c ├── he853.c ├── hid-libusb.c ├── hidapi.h ├── home-easy.c ├── idk.c ├── ook-gpio.c ├── otax.c ├── raw.c ├── raw.h ├── rf-ctrl.c ├── rf-ctrl.conf ├── rf-ctrl.h ├── somfy.c ├── sumtech.c └── sysfs-gpio.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | rf-ctrl 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016 Jean-Christophe Rona 3 | # 4 | # This is free software, licensed under the GNU General Public License v2. 5 | # See /LICENSE for more information. 6 | # 7 | 8 | GNU_PREFIX ?= 9 | 10 | # Allow to create a static binary 11 | STATIC ?= false 12 | 13 | # Use an external implementation of iconv instead of the libc's one 14 | USE_EXTERNAL_LIBICONV ?= false 15 | 16 | # Enable Alsa support 17 | ENABLE_ALSA ?= true 18 | 19 | # Where to install the software 20 | INSTALLATION_PATH ?= /usr/local/bin 21 | 22 | # Where the configuration file should be placed 23 | CONFIGURATION_FILE_LOCATION ?= /etc 24 | 25 | 26 | CONFIGURATION_FILE = $(TARGET).conf 27 | 28 | CFLAGS += -DCONFIG_FILE_LOCATION=\"$(CONFIGURATION_FILE_LOCATION)\" 29 | 30 | ifeq ($(STATIC), true) 31 | LDFLAGS += -static 32 | endif 33 | 34 | CC=$(GNU_PREFIX)gcc 35 | LD=$(GNU_PREFIX)ld 36 | 37 | LDLIBS = -lusb-1.0 -lpthread -lm 38 | 39 | ifeq ($(USE_EXTERNAL_LIBICONV), true) 40 | LDLIBS += -liconv 41 | endif 42 | 43 | ifeq ($(STATIC), true) 44 | LDLIBS += -lgcc_eh 45 | endif 46 | 47 | TARGET = rf-ctrl 48 | OBJECTS = he853.o ook-gpio.o sysfs-gpio.o dummy.o otax.o dio.o home-easy.o idk.o sumtech.o auchan.o auchan2.o somfy.o blyss.o rf-ctrl.o hid-libusb.o raw.o 49 | 50 | ifeq ($(ENABLE_ALSA), true) 51 | LDLIBS += -lasound 52 | CFLAGS += -DALSA_ENABLED 53 | OBJECTS += alsa.o 54 | endif 55 | 56 | all: $(TARGET) 57 | 58 | $(TARGET): $(OBJECTS) 59 | @echo 60 | @echo -n "Linking ..." 61 | @$(CC) $(CFLAGS) $(LDFLAGS) $+ -o $@ $(LDLIBS) 62 | @echo " -> $@" 63 | @echo 64 | 65 | install: 66 | install -D $(TARGET) $(INSTALLATION_PATH) 67 | install -D $(CONFIGURATION_FILE) $(CONFIGURATION_FILE_LOCATION) 68 | 69 | clean: 70 | $(RM) $(OBJECTS) $(TARGET) 71 | 72 | %.o : %.c 73 | @echo "[$@] ..." 74 | @$(CC) $(CFLAGS) -c $< -o $@ 75 | 76 | .PHONY: all clean 77 | -------------------------------------------------------------------------------- /OpenWrt/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016 Jean-Christophe Rona 3 | # 4 | # This is free software, licensed under the GNU General Public License v2. 5 | # See /LICENSE for more information. 6 | # 7 | 8 | include $(TOPDIR)/rules.mk 9 | 10 | PKG_NAME:=rf-ctrl 11 | PKG_VERSION:=0.6 12 | PKG_RELEASE:=1 13 | 14 | PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) 15 | 16 | include $(INCLUDE_DIR)/package.mk 17 | include $(INCLUDE_DIR)/nls.mk 18 | 19 | define Package/rf-ctrl 20 | SECTION:=utils 21 | CATEGORY:=Utilities 22 | TITLE:=433 MHz OOK based devices control utility 23 | DEPENDS:=+libpthread +libusb-1.0 $(ICONV_DEPENDS) 24 | endef 25 | 26 | define Build/Prepare 27 | mkdir -p $(PKG_BUILD_DIR) 28 | $(CP) ./src/* $(PKG_BUILD_DIR)/ 29 | endef 30 | 31 | define Build/Configure 32 | endef 33 | 34 | define Build/Compile 35 | $(MAKE) -C $(PKG_BUILD_DIR) \ 36 | CC="$(TARGET_CC)" \ 37 | CFLAGS="$(TARGET_CFLAGS) -Wall" \ 38 | LDFLAGS="$(TARGET_LDFLAGS)" \ 39 | USE_EXTERNAL_LIBICONV="true" 40 | endef 41 | 42 | define Package/rf-ctrl/install 43 | $(INSTALL_DIR) $(1)/usr/bin 44 | $(INSTALL_BIN) $(PKG_BUILD_DIR)/rf-ctrl $(1)/usr/bin/ 45 | $(INSTALL_DIR) $(1)/etc 46 | $(INSTALL_BIN) $(PKG_BUILD_DIR)/rf-ctrl.conf $(1)/etc 47 | endef 48 | 49 | $(eval $(call BuildPackage,rf-ctrl)) 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rf-ctrl - A command-line tool to control 433MHz OOK based devices 2 | 3 | 4 | ## Synopsis 5 | 6 | rf-ctrl is a command line tool allowing to control wireless plugs and chimes that use a 433MHz OOK modulation. 7 | The OOK modulation (ON/OFF Keying) allows to transmit binary data on a particular frequency by enabling or disabling the transmitter oscillator. I will call these states High and Low. 8 | 9 | A big part of the wireless plugs that can be found on the market uses this modulation with a carrier frequency of 433.92MHz, and almost all of them follow the same frame pattern regarding the transmitted data: 10 | 11 | - a starting marker : High to Low transition with particular High and Low durations 12 | - the actual binary data : 0 and 1 bits are coded using High to Low transitions, with different High and Low timings 13 | - an ending marker : High to Low transition with particular High and Low durations 14 | 15 | This frame is then repeated a specific number of times. Some protocols are based on High to Low transitions, while some others use Low to High transitions. 16 | 17 | rf-ctrl uses a frontend/backend logic allowing to generate the proper frame for a particular protocol on one side, and to use a particular 433MHz enabled hardware to send it on the other side. 18 | This means that adding a new transmitter driver will allow all the supported protocols to be used out of the box. 19 | 20 | 21 | ## Build instruction 22 | 23 | As usual: 24 | ``` 25 | $ make 26 | ``` 27 | 28 | Take a look at the __Makefile__ to see the variables that can be used (for instance CROSS_COMPILE). 29 | 30 | 31 | ## OpenWrt support 32 | 33 | In order to build rf-ctrl as an OpenWrt package and/or add it to an OpenWrt firmware build: 34 | - checkout an OpenWrt build tree (only tested on Barrier Breaker 14.07 so far) 35 | - create the __openwrt/package/utils/rf-ctrl__ sub-folder and place __OpenWrt/Makefile__ in it 36 | - create the __openwrt/package/utils/rf-ctrl/src__ sub-folder and place all the files from the __rf-ctrl__ root folder in it 37 | 38 | Now, if you run `$ make menuconfig` from the __openwrt__ root folder, you should see an __rf-ctrl__ entry in the __Utilities__ sub-menu ! 39 | 40 | 41 | ## Currently supported protocols and transmitters 42 | 43 | The supported transmitters are: 44 | - HE853 : the ELRO/Home Easy USB dongle 45 | - OOK-GPIO : any 1$ 433MHz transmitters connected to a GPIO ([ook-gpio](https://github.com/jcrona/ook-gpio) kernel driver required) 46 | - SYSFS-GPIO : any 1$ 433MHz transmitters connected to a GPIO (using the standard SYSFS GPIO interface) 47 | 48 | The supported protocols are: 49 | - DI-O 50 | - Home Easy (not tested because I don't have one) 51 | - Idk (Chinese wireless chime that is probably sold under different brands) 52 | - OTAX (found in a the French Conforama store) 53 | - Auchan (found in the French Auchan store, and probably sold under different brands) 54 | - Auchan2 (also found in the French Auchan store, and probably sold under different brands) 55 | - Sumtech (not sure were it can be found, it is a pure chinese product like Idk) 56 | - Blyss (found in french Castorama store) 57 | - Somfy (RTS 433,42 MHz only) 58 | 59 | 60 | ## Usage 61 | 62 | Turning off the DI-O device number 3 paired to the remote ID 424242: 63 | ``` 64 | $ sudo ./rf-ctrl -p dio -r 424242 -d 3 -c off 65 | ``` 66 | 67 | Starting a fast RF scan on every OTAX devices, sending the 'on' command: 68 | ``` 69 | $ sudo ./rf-ctrl -p otax -c on -s -n 1 70 | ``` 71 | 72 | 73 | ## License 74 | 75 | rf-ctrl is distributed under the GPLv2 license. See the LICENSE file for more information. 76 | 77 | 78 | ## Miscellaneous 79 | 80 | Thanks to r10r and his [he853-remote](https://github.com/r10r/he853-remote) project. It saved me a lot of time when I implemented the HE853 USB dongle driver and the Home Easy protocol. 81 | So far, rf-ctrl has been successfully tested on a regular Linux PC, a TP-Link TL-WR703N router running OpenWrt, and a Raspberry Pi. 82 | Check out my [Home-RF](https://github.com/jcrona/home-rf) project for a simple Web-UI that uses rf-ctrl. 83 | 84 | Fell free to visit my [blog](http://blog.rona.fr), and/or send me a mail ! 85 | 86 | __ 87 | Copyright (C) 2018 Jean-Christophe Rona 88 | -------------------------------------------------------------------------------- /alsa.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "rf-ctrl.h" 9 | 10 | #define HARDWARE_NAME "Alsa" 11 | 12 | #define SAMPLE_SIZE 2 // 16bits (SND_PCM_FORMAT_S16_LE) 13 | #define SAMPLES_PER_FRAME channels 14 | #define BYTES_PER_FRAME (SAMPLE_SIZE * SAMPLES_PER_FRAME) 15 | 16 | #define ALSA_DEVICE_OUT "default" 17 | 18 | static snd_pcm_t *playback_handle = NULL; 19 | static unsigned int samplerate = 48000; 20 | static unsigned int channels = 2; 21 | static unsigned int periods = 2; 22 | static snd_pcm_format_t sample_format = SND_PCM_FORMAT_S16_LE; 23 | 24 | 25 | static int alsa_configure_device(snd_pcm_t *handle, size_t sample_count) 26 | { 27 | int err; 28 | snd_pcm_hw_params_t *hw_params; 29 | snd_pcm_sw_params_t *sw_params; 30 | snd_pcm_uframes_t period_size; /* Periodsize (frames) */ 31 | snd_pcm_uframes_t buffer_size; /* Buffersize (frames) */ 32 | 33 | if (handle == NULL) { 34 | fprintf(stderr, "%s: Alsa device not open\n", HARDWARE_NAME); 35 | return -1; 36 | } 37 | 38 | /* HW Params */ 39 | if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { 40 | fprintf(stderr, "%s: cannot allocate hardware parameter structure (%s)\n", HARDWARE_NAME, 41 | snd_strerror (err)); 42 | return -1; 43 | } 44 | 45 | if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) { 46 | fprintf (stderr, "%s: cannot initialize hardware parameter structure (%s)\n", HARDWARE_NAME, 47 | snd_strerror (err)); 48 | return -1; 49 | } 50 | 51 | if ((err = snd_pcm_hw_params_set_access(handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { 52 | fprintf(stderr, "%s: cannot set access type (%s)\n", HARDWARE_NAME, 53 | snd_strerror (err)); 54 | return -1; 55 | } 56 | 57 | if ((err = snd_pcm_hw_params_set_format(handle, hw_params, sample_format)) < 0) { 58 | fprintf(stderr, "%s: cannot set sample format (%s)\n", HARDWARE_NAME, 59 | snd_strerror (err)); 60 | return -1; 61 | } 62 | 63 | if ((err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &samplerate, 0)) < 0) { 64 | fprintf(stderr, "%s: cannot set sample rate (%s)\n", HARDWARE_NAME, 65 | snd_strerror (err)); 66 | return -1; 67 | } 68 | dbg_printf(2, "%s: Samplerate: %u\n", HARDWARE_NAME, samplerate); 69 | 70 | if ((err = snd_pcm_hw_params_set_channels(handle, hw_params, channels)) < 0) { 71 | fprintf(stderr, "%s: cannot set channel count (%s)\n", HARDWARE_NAME, 72 | snd_strerror (err)); 73 | return -1; 74 | } 75 | dbg_printf(2, "%s: Channels: %u\n", HARDWARE_NAME, channels); 76 | 77 | if ((err = snd_pcm_hw_params_set_periods_near(handle, hw_params, &periods, 0)) < 0) { 78 | fprintf(stderr, "%s: cannot set period count (%s)\n", HARDWARE_NAME, 79 | snd_strerror (err)); 80 | return -1; 81 | } 82 | dbg_printf(3, "%s: Periods : %u\n", HARDWARE_NAME, periods); 83 | 84 | period_size = (sample_count/SAMPLES_PER_FRAME); 85 | if ((err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &period_size, 0)) < 0) { 86 | fprintf(stderr, "%s: cannot set period size (%s)\n", HARDWARE_NAME, 87 | snd_strerror (err)); 88 | return -1; 89 | } 90 | dbg_printf(3, "%s: Period size : %u\n", HARDWARE_NAME, period_size); 91 | 92 | buffer_size = period_size * periods; 93 | if ((err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &buffer_size)) < 0) { 94 | fprintf(stderr, "%s: cannot set buffer size (%s)\n", HARDWARE_NAME, 95 | snd_strerror (err)); 96 | return -1; 97 | } 98 | dbg_printf(3, "%s: Buffer size: %lu\n", HARDWARE_NAME, (long unsigned int) buffer_size); 99 | 100 | if ((err = snd_pcm_hw_params(handle, hw_params)) < 0) { 101 | fprintf(stderr, "%s: cannot set parameters (%s)\n", HARDWARE_NAME, 102 | snd_strerror (err)); 103 | return -1; 104 | } 105 | 106 | /* SW Params */ 107 | if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) { 108 | fprintf(stderr, "%s: cannot allocate software parameter structure (%s)\n", HARDWARE_NAME, 109 | snd_strerror (err)); 110 | return -1; 111 | } 112 | 113 | if (snd_pcm_sw_params_current(handle, sw_params) < 0) { 114 | fprintf(stderr, "%s: cannot get sw params (%s)\n", HARDWARE_NAME, 115 | snd_strerror (err)); 116 | return -1; 117 | } 118 | 119 | /* Start playing after the first write */ 120 | if (snd_pcm_sw_params_set_start_threshold(handle, sw_params, 1) < 0) { 121 | fprintf(stderr, "%s: cannot set start threshold (%s)\n", HARDWARE_NAME, 122 | snd_strerror (err)); 123 | return -1; 124 | } 125 | 126 | /* Wake up on every interrupt */ 127 | if (snd_pcm_sw_params_set_avail_min(handle, sw_params, 1) < 0) { 128 | fprintf(stderr, "%s: cannot set min avail (%s)\n", HARDWARE_NAME, 129 | snd_strerror (err)); 130 | return -1; 131 | } 132 | 133 | if (snd_pcm_sw_params(handle, sw_params) < 0) { 134 | fprintf(stderr, "%s: cannot set sw params (%s)\n", HARDWARE_NAME, 135 | snd_strerror (err)); 136 | return -1; 137 | } 138 | 139 | snd_pcm_hw_params_free(hw_params); 140 | snd_pcm_sw_params_free(sw_params); 141 | 142 | return 0; 143 | } 144 | 145 | static int alsa_open_playback(char * device_out) 146 | { 147 | int err; 148 | 149 | if (playback_handle == NULL) { 150 | if ((err = snd_pcm_open(&playback_handle, device_out, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { 151 | fprintf(stderr, "%s: cannot open audio device %s (playback) (%s)\n", HARDWARE_NAME, 152 | device_out, 153 | snd_strerror (err)); 154 | return -1; 155 | } 156 | } 157 | 158 | return 0; 159 | } 160 | 161 | static void alsa_close_playback(void) 162 | { 163 | if (playback_handle == NULL) 164 | fprintf(stderr, "%s: Alsa device not open (playback)\n", HARDWARE_NAME); 165 | else { 166 | snd_pcm_close(playback_handle); 167 | playback_handle = NULL; 168 | } 169 | 170 | return; 171 | } 172 | 173 | static int alsa_write_audio(uint16_t *buf, size_t sample_count) { 174 | int ret = 0; 175 | int err; 176 | 177 | dbg_printf(2, "%s: Number of samples to write: %u\n", HARDWARE_NAME, sample_count); 178 | 179 | if (playback_handle == NULL) { 180 | fprintf(stderr, "%s: Alsa device not open\n", HARDWARE_NAME); 181 | return -1; 182 | } 183 | 184 | if (alsa_configure_device(playback_handle, sample_count) < 0) { 185 | fprintf(stderr, "%s: device configuration failed\n", HARDWARE_NAME); 186 | return -1; 187 | } 188 | 189 | if ((err = snd_pcm_prepare(playback_handle)) < 0) { 190 | fprintf(stderr, "%s: cannot prepare audio interface for use (%s)\n", HARDWARE_NAME, 191 | snd_strerror (err)); 192 | return -1; 193 | } 194 | 195 | if ((err = snd_pcm_writei(playback_handle, buf, sample_count/SAMPLES_PER_FRAME)) != sample_count/SAMPLES_PER_FRAME) { 196 | fprintf(stderr, "%s: Write error (%s)\n", HARDWARE_NAME, snd_strerror (err)); 197 | } 198 | 199 | snd_pcm_drain(playback_handle); 200 | 201 | return ret; 202 | } 203 | 204 | static uint16_t * alsa_convert_frame_to_audio(size_t *sample_count, struct timing_config *config, uint8_t *src_frame, size_t src_bit_count) 205 | { 206 | int ret, i, j, k; 207 | uint16_t *dest_frame; 208 | size_t count; 209 | uint32_t samples_per_bit = round((config->base_time * samplerate)/1000000.0f); 210 | size_t samples_per_rf_frame = src_bit_count * samples_per_bit * channels; 211 | 212 | dbg_printf(2, "%s: Samples per bit: %u\n", HARDWARE_NAME, samples_per_bit); 213 | 214 | *sample_count = samples_per_rf_frame * config->frame_count; 215 | if (*sample_count < 16000) { 216 | /* Be sure the final buffer will be big enough to prevent underruns */ 217 | *sample_count = 16000; 218 | } 219 | 220 | dest_frame = (uint16_t *) malloc(sizeof(uint16_t) * (*sample_count)); 221 | if(!dest_frame) { 222 | fprintf(stderr, "%s: cannot allocate memory for audio buffer\n", HARDWARE_NAME); 223 | return NULL; 224 | } 225 | 226 | memset(dest_frame, 0, sizeof(uint16_t) * (*sample_count)); 227 | 228 | for (k = 0; k < config->frame_count; k++) { 229 | for (i = 0; i < src_bit_count; i++) { 230 | if (src_frame[i/8] & (1 << (7 - (i % 8)))) { 231 | for (j = 0; j < samples_per_bit; j++) { 232 | dest_frame[k * samples_per_rf_frame + 2 * (i * samples_per_bit + j) + 1] = 0x8000; 233 | } 234 | } else { 235 | for (j = 0; j < samples_per_bit; j++) { 236 | dest_frame[k * samples_per_rf_frame + 2 * (i * samples_per_bit + j) + 1] = 0x7FFF; 237 | } 238 | } 239 | } 240 | } 241 | 242 | return dest_frame; 243 | } 244 | 245 | static int alsa_probe(void) { 246 | /* This driver cannot be auto-detected */ 247 | return -1; 248 | } 249 | 250 | static int alsa_init(struct rf_hardware_params *params) { 251 | return alsa_open_playback(ALSA_DEVICE_OUT); 252 | } 253 | 254 | static void alsa_close(void) { 255 | alsa_close_playback(); 256 | } 257 | 258 | static int alsa_send_cmd(struct timing_config *config, uint8_t *frame_data, uint16_t bit_count) { 259 | int ret = -1; 260 | size_t sample_count = 0; 261 | uint16_t *audio_data = alsa_convert_frame_to_audio(&sample_count, config, frame_data, bit_count); 262 | 263 | if (audio_data != NULL) { 264 | ret = alsa_write_audio(audio_data, sample_count); 265 | free(audio_data); 266 | } 267 | 268 | return ret; 269 | } 270 | 271 | struct rf_hardware_driver alsa_driver = { 272 | .name = HARDWARE_NAME, 273 | .cmd_name = "alsa", 274 | .long_name = "Alsa 433MHz Differential RF Transceiver", 275 | .supported_bit_fmts = (1 << RF_BIT_FMT_RAW), 276 | .probe = &alsa_probe, 277 | .init = &alsa_init, 278 | .close = &alsa_close, 279 | .send_cmd = &alsa_send_cmd, 280 | }; 281 | -------------------------------------------------------------------------------- /auchan.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Reversed protocol of Auchan wireless plugs 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "rf-ctrl.h" 28 | 29 | #define PROTOCOL_NAME "Auchan" 30 | 31 | 32 | static int auchan_format_cmd(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command) { 33 | const int bit_count = 25; 34 | uint8_t raw_cmd; // 1 bits 35 | 36 | if (data_len * 8 < bit_count) { 37 | fprintf(stderr, "%s: data buffer too small (%lu available, %d needed)\n", PROTOCOL_NAME, (unsigned long) data_len, (bit_count + 7)/8); 38 | return -1; 39 | } 40 | 41 | switch (command) { 42 | case RF_CMD_OFF: 43 | raw_cmd = 0x1; 44 | break; 45 | 46 | case RF_CMD_ON: 47 | raw_cmd = 0x0; 48 | break; 49 | 50 | default: 51 | fprintf(stderr, "%s: Unsupported command %d\n", PROTOCOL_NAME, (int) command); 52 | return -1; 53 | } 54 | 55 | data[0] = (remote_code & 0xFFFFF) >> 12; 56 | data[1] = (remote_code & 0xFFF) >> 4; 57 | data[2] = ((remote_code & 0xF) << 4) | ((device_code & 0x7) << 1) | (raw_cmd & 0x1); 58 | data[3] = 0; 59 | 60 | return bit_count; 61 | } 62 | 63 | static struct timing_config auchan_timings = { 64 | .start_bit_h_time = 0, // 0 us 65 | .start_bit_l_time = 10400, // 10400 us 66 | .end_bit_h_time = 0, // 0 us 67 | .end_bit_l_time = 0, // 0 us 68 | .data_bit0_h_time = 400, // 400 us 69 | .data_bit0_l_time = 1100, // 1100 us 70 | .data_bit1_h_time = 1100, // 1100 us 71 | .data_bit1_l_time = 400, // 400 us 72 | .bit_fmt = RF_BIT_FMT_HL, 73 | .frame_count = 10, 74 | }; 75 | 76 | struct rf_protocol_driver auchan_driver = { 77 | .name = PROTOCOL_NAME, 78 | .cmd_name = "auchan", 79 | .timings = &auchan_timings, 80 | .format_cmd = &auchan_format_cmd, 81 | .remote_code_max = 0xFFFFF, 82 | .device_code_max = 0x7, 83 | .needed_params = PARAM_REMOTE_ID | PARAM_DEVICE_ID | PARAM_COMMAND, 84 | }; 85 | -------------------------------------------------------------------------------- /auchan2.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Reversed protocol of the new Auchan wireless plugs 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "rf-ctrl.h" 28 | 29 | #define PROTOCOL_NAME "Auchan 2" 30 | 31 | 32 | static int auchan2_format_cmd(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command) { 33 | const int bit_count = 21; 34 | uint8_t raw_cmd; // 2 bits 35 | uint8_t crc = 0; // 2 bits 36 | int i; 37 | 38 | if (data_len * 8 < bit_count) { 39 | fprintf(stderr, "%s: data buffer too small (%lu available, %d needed)\n", PROTOCOL_NAME, (unsigned long) data_len, (bit_count + 7)/8); 40 | return -1; 41 | } 42 | 43 | switch (command) { 44 | case RF_CMD_OFF: 45 | raw_cmd = 0x00; 46 | break; 47 | 48 | case RF_CMD_ON: 49 | raw_cmd = 0x01; 50 | break; 51 | 52 | case RF_CMD_GOFF: 53 | raw_cmd = 0x02; 54 | break; 55 | 56 | case RF_CMD_GON: 57 | raw_cmd = 0x03; 58 | break; 59 | 60 | default: 61 | fprintf(stderr, "%s: Unsupported command %d\n", PROTOCOL_NAME, (int) command); 62 | return -1; 63 | } 64 | 65 | data[0] = (remote_code & 0x1FFF) >> 5; 66 | data[1] = ((remote_code & 0x1F) << 3) | ((device_code & 0x1) << 2) | (device_code & 0x2) | ((raw_cmd & 0x3) >> 1); 67 | data[2] = ((raw_cmd & 0x1) << 7); 68 | 69 | /* 2 bits XOR of the frame by 2-bits steps (the first bit of the frame will be alone) */ 70 | for (i = 0; i < bit_count - 2; i++) { 71 | crc ^= ((data[i/8] >> (7 - (i % 8))) & 0x1) << (i % 2); 72 | } 73 | 74 | data[2] |= ((crc & 0x3) << 3); 75 | 76 | return bit_count; 77 | } 78 | 79 | static struct timing_config auchan2_timings = { 80 | .start_bit_h_time = 0, // 0 us 81 | .start_bit_l_time = 0, // 0 us 82 | .end_bit_h_time = 0, // 0 us 83 | .end_bit_l_time = 0, // 0 us 84 | .data_bit0_h_time = 1320, // 1320 us 85 | .data_bit0_l_time = 680, // 680 us 86 | .data_bit1_h_time = 680, // 680 us 87 | .data_bit1_l_time = 1320, // 1320 us 88 | .bit_fmt = RF_BIT_FMT_LH, 89 | .frame_count = 10, 90 | }; 91 | 92 | struct rf_protocol_driver auchan2_driver = { 93 | .name = PROTOCOL_NAME, 94 | .cmd_name = "auchan2", 95 | .timings = &auchan2_timings, 96 | .format_cmd = &auchan2_format_cmd, 97 | .remote_code_max = 0x1FFF, 98 | .device_code_max = 0x3, 99 | .needed_params = PARAM_REMOTE_ID | PARAM_DEVICE_ID | PARAM_COMMAND, 100 | }; 101 | -------------------------------------------------------------------------------- /blyss.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Reversed protocol of Blyss wireless plugs 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "rf-ctrl.h" 28 | 29 | #define PROTOCOL_NAME "Blyss" 30 | 31 | #define FRAME_TYPE_SWITCH 0xFE; 32 | #define FRAME_TYPE_TEMP_HUM 0xE5; 33 | 34 | struct rf_protocol_driver blyss_driver; 35 | 36 | uint8_t rolling_code_table[5] = {0x98, 0xDA, 0x1E, 0xE6, 0x67}; 37 | 38 | 39 | static int blyss_format_cmd(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command) { 40 | const int bit_count = 52; 41 | uint8_t raw_cmd; // 1 bit 42 | uint8_t rolling_code_idx = 0; 43 | uint8_t timestamp = 0; 44 | uint8_t channel = 0; // 4 bits 45 | char data_file_path[STORAGE_PATH_MAX_LEN]; 46 | FILE * f; 47 | 48 | if (data_len * 8 < bit_count) { 49 | fprintf(stderr, "%s: data buffer too small (%lu available, %d needed)\n", PROTOCOL_NAME, (unsigned long) data_len, (bit_count + 7)/8); 50 | return -1; 51 | } 52 | 53 | switch (command) { 54 | case RF_CMD_OFF: 55 | raw_cmd = 0x1; 56 | break; 57 | 58 | case RF_CMD_ON: 59 | raw_cmd = 0x0; 60 | break; 61 | 62 | case RF_CMD_GOFF: 63 | raw_cmd = 0x1; 64 | device_code = 0; 65 | break; 66 | 67 | case RF_CMD_GON: 68 | raw_cmd = 0x0; 69 | device_code = 0; 70 | break; 71 | 72 | default: 73 | fprintf(stderr, "%s: Unsupported command %d\n", PROTOCOL_NAME, (int) command); 74 | return -1; 75 | } 76 | 77 | /* Get the current rolling code for that device, remote, and channel */ 78 | get_storage_path(data_file_path, &blyss_driver); 79 | snprintf(data_file_path + strlen(data_file_path), STORAGE_PATH_MAX_LEN, "/%02X.%06X", remote_code, device_code); 80 | 81 | dbg_printf(3, "%s: Data file is %s\n", PROTOCOL_NAME, data_file_path); 82 | 83 | f = fopen(data_file_path, "r+"); 84 | if (f == NULL) { 85 | f = fopen(data_file_path, "w+"); 86 | if (f == NULL) { 87 | fprintf(stderr, "%s: Cannot open %s\n", PROTOCOL_NAME, data_file_path); 88 | return -1; 89 | } 90 | } 91 | 92 | if (fscanf(f, "%01hhX", &rolling_code_idx) != 1) { 93 | dbg_printf(2, "%s: Rolling code initialized to 0\n", PROTOCOL_NAME); 94 | } 95 | 96 | fseek(f, 0, SEEK_SET); 97 | fprintf(f, "%01X\n", (rolling_code_idx + 1) % 5); 98 | 99 | fclose(f); 100 | 101 | dbg_printf(1, "%s: Rolling code index at %u (0x%02X)\n", PROTOCOL_NAME, rolling_code_idx, rolling_code_table[rolling_code_idx]); 102 | 103 | /* We do not really care about this one as long as it is changing */ 104 | timestamp = ~rolling_code_table[rolling_code_idx]; 105 | 106 | /* Use the first 4 bits of remote_code as channel */ 107 | channel = (remote_code >> 16) & 0xF; 108 | remote_code &= 0xFFFF; 109 | 110 | data[0] = FRAME_TYPE_SWITCH; 111 | data[1] = ((channel & 0x0F) << 4) | ((remote_code >> 12) & 0x0F); 112 | data[2] = (remote_code >> 4) & 0xFF; 113 | data[3] = ((remote_code & 0x0F) << 4 ) | (device_code & 0x0F); 114 | data[4] = ((raw_cmd & 0x0F) << 4 ) | ((rolling_code_table[rolling_code_idx] >> 4) & 0x0F); 115 | data[5] = ((rolling_code_table[rolling_code_idx] & 0x0F) << 4) | ((timestamp >> 4) & 0x0F); 116 | data[6] = (timestamp & 0x0F) << 4; 117 | 118 | return bit_count; 119 | } 120 | 121 | static struct timing_config blyss_timings = { 122 | .start_bit_h_time = 2400, // 2400 us 123 | .start_bit_l_time = 0, // 0 us 124 | .end_bit_h_time = 0, // 0 us 125 | .end_bit_l_time = 24000, // 24000 us 126 | .data_bit0_h_time = 400, // 400 us 127 | .data_bit0_l_time = 800, // 800 us 128 | .data_bit1_h_time = 800, // 800 us 129 | .data_bit1_l_time = 400, // 400 us 130 | .bit_fmt = RF_BIT_FMT_LH, 131 | .frame_count = 10, 132 | }; 133 | 134 | struct rf_protocol_driver blyss_driver = { 135 | .name = PROTOCOL_NAME, 136 | .cmd_name = "blyss", 137 | .timings = &blyss_timings, 138 | .format_cmd = &blyss_format_cmd, 139 | .remote_code_max = 0xFFFFF, 140 | .device_code_max = 0xF, 141 | .needed_params = PARAM_REMOTE_ID | PARAM_DEVICE_ID | PARAM_COMMAND, 142 | }; 143 | -------------------------------------------------------------------------------- /dio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Reversed protocol of DI-O wireless plugs 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "rf-ctrl.h" 28 | 29 | #define PROTOCOL_NAME "DI-O" 30 | 31 | 32 | static int dio_format_cmd(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command) { 33 | const int bit_count = 64; 34 | uint8_t buf[4]; 35 | uint8_t raw_cmd; 36 | int i; 37 | 38 | if (data_len * 8 < bit_count) { 39 | fprintf(stderr, "%s: data buffer too small (%lu available, %d needed)\n", PROTOCOL_NAME, (unsigned long) data_len, (bit_count + 7)/8); 40 | return -1; 41 | } 42 | 43 | switch (command) { 44 | case RF_CMD_OFF: 45 | raw_cmd = 0x00; 46 | break; 47 | 48 | case RF_CMD_ON: 49 | raw_cmd = 0x01; 50 | break; 51 | 52 | case RF_CMD_GOFF: 53 | raw_cmd = 0x02; 54 | break; 55 | 56 | case RF_CMD_GON: 57 | raw_cmd = 0x03; 58 | break; 59 | 60 | default: 61 | fprintf(stderr, "%s: Unsupported command %d\n", PROTOCOL_NAME, (int) command); 62 | return -1; 63 | } 64 | 65 | buf[0] = (remote_code >> 18) & 0xFF; 66 | buf[1] = (remote_code >> 10) & 0xFF; 67 | buf[2] = (remote_code >> 2) & 0xFF; 68 | buf[3] = ((remote_code & 0x03) << 6 ) | ((raw_cmd & 0x03) << 4) | (device_code & 0x0F); 69 | 70 | /* Manchester */ 71 | memset(data, 0, data_len); 72 | for (i = 0; i < 32; i++) { 73 | if (buf[i/8] & (0x01 << (i % 8))) { 74 | data[(i/8) * 2 + 1 - ((i/4) % 2)] |= 0x02 << ((i % 4) * 2); 75 | } else { 76 | data[(i/8) * 2 + 1 - ((i/4) % 2)] |= 0x01 << ((i % 4) * 2); 77 | } 78 | } 79 | 80 | return bit_count; 81 | } 82 | 83 | static struct timing_config dio_timings = { 84 | .start_bit_h_time = 260, // 260 us 85 | .start_bit_l_time = 2680, // 2680 us 86 | .end_bit_h_time = 260, // 260 us 87 | .end_bit_l_time = 9000, // 9000 us 88 | .data_bit0_h_time = 260, // 260 us 89 | .data_bit0_l_time = 260, // 260 us 90 | .data_bit1_h_time = 260, // 260 us 91 | .data_bit1_l_time = 1300, // 1300 us 92 | .bit_fmt = RF_BIT_FMT_HL, 93 | .frame_count = 7, 94 | }; 95 | 96 | struct rf_protocol_driver dio_driver = { 97 | .name = PROTOCOL_NAME, 98 | .cmd_name = "dio", 99 | .timings = &dio_timings, 100 | .format_cmd = &dio_format_cmd, 101 | .remote_code_max = 0x3FFFFFF, 102 | .device_code_max = 0x0F, 103 | .needed_params = PARAM_REMOTE_ID | PARAM_DEVICE_ID | PARAM_COMMAND, 104 | }; 105 | -------------------------------------------------------------------------------- /dummy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Dummy driver for test purpose 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "rf-ctrl.h" 28 | 29 | #define HARDWARE_NAME "Dummy HW" 30 | 31 | #define BASE_TIME_HL 100 // us 32 | #define H_CHAR 0x18 33 | #define L_CHAR 0x5F 34 | 35 | 36 | static int dummy_probe(void) { 37 | /* Prevent from being auto-detected */ 38 | return -1; 39 | } 40 | 41 | static int dummy_init(struct rf_hardware_params *params) { 42 | return 0; 43 | } 44 | 45 | static void dummy_close(void) { 46 | } 47 | 48 | static void dbg_printnc(int level, char c, uint16_t n) { 49 | uint16_t i; 50 | 51 | for (i = 0; i < n; i++) { 52 | dbg_printf(level, "%c", c); 53 | } 54 | } 55 | 56 | static int dummy_send_cmd(struct timing_config *config, uint8_t *frame_data, uint16_t bit_count) { 57 | uint16_t i; 58 | 59 | if (!is_dbg_enabled(1)) { 60 | return 0; 61 | } 62 | 63 | dbg_printf(1, "%s: Count = %u\n", HARDWARE_NAME, config->frame_count); 64 | 65 | dbg_printf(1, "%s: Frame = ", HARDWARE_NAME); 66 | switch (config->bit_fmt) { 67 | case RF_BIT_FMT_HL: 68 | dbg_printnc(1, H_CHAR, config->start_bit_h_time/BASE_TIME_HL); 69 | dbg_printnc(1, L_CHAR, config->start_bit_l_time/BASE_TIME_HL); 70 | break; 71 | 72 | case RF_BIT_FMT_LH: 73 | dbg_printnc(1, L_CHAR, config->start_bit_l_time/BASE_TIME_HL); 74 | dbg_printnc(1, H_CHAR, config->start_bit_h_time/BASE_TIME_HL); 75 | break; 76 | } 77 | 78 | for (i = 0; i < bit_count; i++) { 79 | if (frame_data[i/8] & (1 << (7 - (i % 8)))) { 80 | switch (config->bit_fmt) { 81 | case RF_BIT_FMT_HL: 82 | dbg_printnc(1, H_CHAR, config->data_bit1_h_time/BASE_TIME_HL); 83 | dbg_printnc(1, L_CHAR, config->data_bit1_l_time/BASE_TIME_HL); 84 | break; 85 | 86 | case RF_BIT_FMT_LH: 87 | dbg_printnc(1, L_CHAR, config->data_bit1_l_time/BASE_TIME_HL); 88 | dbg_printnc(1, H_CHAR, config->data_bit1_h_time/BASE_TIME_HL); 89 | break; 90 | 91 | case RF_BIT_FMT_RAW: 92 | dbg_printnc(1, H_CHAR, 1); 93 | break; 94 | 95 | } 96 | } else { 97 | switch (config->bit_fmt) { 98 | case RF_BIT_FMT_HL: 99 | dbg_printnc(1, H_CHAR, config->data_bit0_h_time/BASE_TIME_HL); 100 | dbg_printnc(1, L_CHAR, config->data_bit0_l_time/BASE_TIME_HL); 101 | break; 102 | 103 | case RF_BIT_FMT_LH: 104 | dbg_printnc(1, L_CHAR, config->data_bit0_l_time/BASE_TIME_HL); 105 | dbg_printnc(1, H_CHAR, config->data_bit0_h_time/BASE_TIME_HL); 106 | break; 107 | 108 | case RF_BIT_FMT_RAW: 109 | dbg_printnc(1, L_CHAR, 1); 110 | break; 111 | 112 | } 113 | } 114 | } 115 | 116 | switch (config->bit_fmt) { 117 | case RF_BIT_FMT_HL: 118 | dbg_printnc(1, H_CHAR, config->end_bit_h_time/BASE_TIME_HL); 119 | dbg_printnc(1, L_CHAR, config->end_bit_l_time/BASE_TIME_HL); 120 | break; 121 | 122 | case RF_BIT_FMT_LH: 123 | dbg_printnc(1, L_CHAR, config->end_bit_l_time/BASE_TIME_HL); 124 | dbg_printnc(1, H_CHAR, config->end_bit_h_time/BASE_TIME_HL); 125 | break; 126 | } 127 | 128 | dbg_printf(1, "\n"); 129 | 130 | return 0; 131 | } 132 | 133 | struct rf_hardware_driver dummy_driver = { 134 | .name = HARDWARE_NAME, 135 | .cmd_name = "dummy", 136 | .long_name = "Dummy Hardware", 137 | .supported_bit_fmts = (1 << RF_BIT_FMT_HL) | (1 << RF_BIT_FMT_LH) | (1 << RF_BIT_FMT_RAW), 138 | .probe = &dummy_probe, 139 | .init = &dummy_init, 140 | .close = &dummy_close, 141 | .send_cmd = &dummy_send_cmd, 142 | }; 143 | -------------------------------------------------------------------------------- /he853.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * ELRO/HomeEasy HE853 USB Dongle driver 4 | * Thanks to https://github.com/r10r/he853-remote for the HE853 USB commands 5 | * 6 | * Copyright (C) 2018 Jean-Christophe Rona 7 | * 8 | * This program is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU General Public License 10 | * as published by the Free Software Foundation; either version 2 11 | * of the License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "rf-ctrl.h" 29 | #include "hidapi.h" 30 | 31 | #define HARDWARE_NAME "HE853" 32 | 33 | #define HE853_PID 0x1357 34 | #define HE853_VID 0x04d9 35 | 36 | #define HE853_CMD_TIMING1 0x01 37 | #define HE853_CMD_TIMING2 0x02 38 | #define HE853_CMD_DATA1 0x03 39 | #define HE853_CMD_DATA2 0x04 40 | #define HE853_CMD_EXECUTE 0x05 41 | 42 | #define HE853_DATA_LEN_MAX 14 43 | 44 | #define MAX_HID_WRITE_ATTEMPT 5 45 | 46 | hid_device *handle = NULL; 47 | 48 | 49 | static int he853_probe(void) { 50 | hid_device *h; 51 | 52 | h = hid_open(HE853_VID, HE853_PID, NULL); 53 | 54 | if (!h) { 55 | dbg_printf(2, "%s not detected\n", HARDWARE_NAME); 56 | return -1; 57 | } 58 | 59 | hid_close(h); 60 | 61 | dbg_printf(2, "%s detected\n", HARDWARE_NAME); 62 | 63 | return 0; 64 | } 65 | 66 | static int he853_init(struct rf_hardware_params *params) { 67 | if (handle != NULL) { 68 | dbg_printf(1, "%s device already initialized\n", HARDWARE_NAME); 69 | return 0; 70 | } 71 | 72 | handle = hid_open(HE853_VID, HE853_PID, NULL); 73 | 74 | if (!handle) { 75 | fprintf(stderr, "%s: Unable to open device\n", HARDWARE_NAME); 76 | return -1; 77 | } 78 | 79 | return 0; 80 | } 81 | 82 | static void he853_close(void) { 83 | hid_close(handle); 84 | 85 | handle = NULL; 86 | } 87 | 88 | static int he853_send_hid_report(uint8_t* buf) { 89 | uint8_t obuf[9]; 90 | int ret = 0; 91 | int i, retry_count = 0; 92 | 93 | obuf[0] = 0x00; // report id = 0, as it seems to be the only report 94 | 95 | for (i = 0; i < 8; i++) { 96 | obuf[i + 1] = buf[i]; 97 | } 98 | 99 | if (is_dbg_enabled(2)) { 100 | dbg_printf(2, " %s HID report: ", HARDWARE_NAME); 101 | for (i = 0; i < 9; i++) { 102 | dbg_printf(2, "%02X ", obuf[i]); 103 | } 104 | dbg_printf(2, "\n"); 105 | } 106 | 107 | while (retry_count < MAX_HID_WRITE_ATTEMPT) { 108 | ret = hid_write(handle, obuf, 9); 109 | if (ret < 0) { 110 | retry_count++; 111 | dbg_printf(2, "%s: Warning, hid_write failed, retrying (%d)...\n", HARDWARE_NAME, retry_count); 112 | } else { 113 | break; 114 | } 115 | } 116 | 117 | if (ret < 0) { 118 | fprintf(stderr, "%s: Cannot send HID report, hid_write failed (id %d, command %d) !\n", HARDWARE_NAME, obuf[0], obuf[1]); 119 | } 120 | 121 | return ret; 122 | } 123 | 124 | static int he853_send_rf_frame(void) { 125 | uint8_t cmd_buf[8]; 126 | 127 | memset(cmd_buf, 0x00, sizeof(cmd_buf)); 128 | cmd_buf[0] = HE853_CMD_EXECUTE; 129 | 130 | return he853_send_hid_report(cmd_buf); 131 | } 132 | 133 | /* 134 | * The HE853 dongle is supposed to take timing values 135 | * in 10 us unit, but it appears it does not work for 136 | * "low" ( < 9000 us) values, and the resulting timings 137 | * also differ between H and L values. 138 | */ 139 | static uint16_t to_he853_htime(uint16_t usec) { 140 | switch (usec/10) { 141 | case 0: 142 | return 0x00; 143 | 144 | case 16: 145 | return 0x25; 146 | 147 | case 22: 148 | return 0x2A; 149 | 150 | case 26: 151 | return 0x2D; 152 | 153 | case 40: 154 | return 0x30; 155 | 156 | case 42: 157 | return 0x33; 158 | 159 | case 70: 160 | return 0x5C; 161 | 162 | case 110: 163 | return 0x76; 164 | 165 | default: 166 | fprintf(stderr, "%s: %d us is not supported as H time !\n", HARDWARE_NAME, usec); 167 | return 0; 168 | } 169 | } 170 | 171 | static uint16_t to_he853_ltime(uint16_t usec) { 172 | switch (usec/10) { 173 | case 0: 174 | return 0x00; 175 | 176 | case 16: 177 | return 0x04; 178 | 179 | case 26: 180 | return 0x09; 181 | 182 | case 32: 183 | return 0x0C; 184 | 185 | case 40: 186 | return 0x1F; 187 | 188 | case 42: 189 | return 0x18; 190 | 191 | case 80: 192 | return 0x3C; 193 | 194 | case 110: 195 | return 0x63; 196 | 197 | case 130: 198 | return 0x68; 199 | 200 | case 268: 201 | return 0xF8; 202 | 203 | case 486: 204 | return 0x01E0; 205 | 206 | case 744: 207 | return 0x02E3; 208 | 209 | case 860: 210 | return 0x035A; 211 | 212 | case 900: 213 | return 0x0384; 214 | 215 | case 1040: 216 | return 0x0410; 217 | 218 | default: 219 | fprintf(stderr, "%s: %d us is not supported as L time !\n", HARDWARE_NAME, usec); 220 | return 0; 221 | } 222 | } 223 | 224 | static int he853_configure(struct timing_config *conf, uint8_t *frame_data, uint8_t bit_count) { 225 | uint8_t cmd_buf[32]; 226 | uint8_t frame_len = (bit_count + 7)/8; 227 | uint16_t sbit_htime; 228 | uint16_t sbit_ltime; 229 | uint16_t ebit_htime; 230 | uint16_t ebit_ltime; 231 | uint8_t dbit0_htime; 232 | uint8_t dbit0_ltime; 233 | uint8_t dbit1_htime; 234 | uint8_t dbit1_ltime; 235 | int i; 236 | 237 | if (conf->bit_fmt != RF_BIT_FMT_HL) { 238 | fprintf(stderr, "%s: Bit format %s is not supported !\n", HARDWARE_NAME, rf_bit_fmt_str[(int) conf->bit_fmt]); 239 | return -1; 240 | } 241 | 242 | /* Convert from real timings to HE853 timings */ 243 | sbit_htime = to_he853_htime(conf->start_bit_h_time); 244 | sbit_ltime = to_he853_ltime(conf->start_bit_l_time); 245 | ebit_htime = to_he853_htime(conf->end_bit_h_time); 246 | ebit_ltime = to_he853_ltime(conf->end_bit_l_time); 247 | dbit0_htime = to_he853_htime(conf->data_bit0_h_time); 248 | dbit0_ltime = to_he853_ltime(conf->data_bit0_l_time); 249 | dbit1_htime = to_he853_htime(conf->data_bit1_h_time); 250 | dbit1_ltime = to_he853_ltime(conf->data_bit1_l_time); 251 | 252 | /* The HE853 dongle needs a positive H time in order to process the L time */ 253 | if (sbit_ltime != 0 && sbit_htime == 0) { 254 | sbit_htime = 1; 255 | } 256 | if (ebit_ltime != 0 && ebit_htime == 0) { 257 | ebit_htime = 1; 258 | } 259 | if (dbit0_ltime != 0 && dbit0_htime == 0) { 260 | dbit0_htime = 1; 261 | } 262 | if (dbit1_ltime != 0 && dbit1_htime == 0) { 263 | dbit1_htime = 1; 264 | } 265 | 266 | cmd_buf[0] = HE853_CMD_TIMING1; 267 | cmd_buf[1] = (uint8_t) (sbit_htime >> 8); 268 | cmd_buf[2] = (uint8_t) (sbit_htime); 269 | cmd_buf[3] = (uint8_t) (sbit_ltime >> 8); 270 | cmd_buf[4] = (uint8_t) (sbit_ltime); 271 | cmd_buf[5] = (uint8_t) (ebit_htime >> 8); 272 | cmd_buf[6] = (uint8_t) (ebit_htime); 273 | cmd_buf[7] = (uint8_t) (ebit_ltime >> 8); 274 | 275 | cmd_buf[8] = HE853_CMD_TIMING2; 276 | cmd_buf[9] = (uint8_t) (ebit_ltime); 277 | cmd_buf[10] = dbit0_htime; 278 | cmd_buf[11] = dbit0_ltime; 279 | cmd_buf[12] = dbit1_htime; 280 | cmd_buf[13] = dbit1_ltime; 281 | cmd_buf[14] = (uint8_t) bit_count; 282 | cmd_buf[15] = (uint8_t) (conf->frame_count); 283 | 284 | cmd_buf[16] = HE853_CMD_DATA1; 285 | for (i = 0; i < 7; i++) { 286 | if (i < frame_len) { 287 | cmd_buf[17 + i] = frame_data[i]; 288 | } else { 289 | cmd_buf[17 + i] = 0x00; 290 | } 291 | } 292 | 293 | cmd_buf[24] = HE853_CMD_DATA2; 294 | for (i = 0; i < 7; i++) { 295 | if (i + 7 < frame_len) { 296 | cmd_buf[25 + i] = frame_data[i + 7]; 297 | } else { 298 | cmd_buf[25 + i] = 0x00; 299 | } 300 | } 301 | 302 | return he853_send_hid_report(cmd_buf) 303 | && he853_send_hid_report(cmd_buf+8) 304 | && he853_send_hid_report(cmd_buf+16) 305 | && he853_send_hid_report(cmd_buf+24); 306 | } 307 | 308 | static int he853_send_cmd(struct timing_config *config, uint8_t *frame_data, uint16_t bit_count) { 309 | int ret = 0; 310 | 311 | if (bit_count > 255) { 312 | fprintf(stderr, "%s: Frame is too long !\n", HARDWARE_NAME); 313 | return -1; 314 | } 315 | 316 | ret = he853_configure(config, frame_data, (uint8_t) bit_count); 317 | if (ret < 0) { 318 | fprintf(stderr, "%s configuration failed\n", HARDWARE_NAME); 319 | return ret; 320 | } 321 | 322 | return he853_send_rf_frame(); 323 | } 324 | 325 | struct rf_hardware_driver he853_driver = { 326 | .name = HARDWARE_NAME, 327 | .cmd_name = "he853", 328 | .long_name = "HE853 USB RF dongle", 329 | .supported_bit_fmts = (1 << RF_BIT_FMT_HL), 330 | .probe = &he853_probe, 331 | .init = &he853_init, 332 | .close = &he853_close, 333 | .send_cmd = &he853_send_cmd, 334 | }; 335 | -------------------------------------------------------------------------------- /hid-libusb.c: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | Alan Ott 6 | Signal 11 Software 7 | 8 | 8/22/2009 9 | Linux Version - 6/2/2010 10 | Libusb Version - 8/13/2010 11 | 12 | Copyright 2009, All Rights Reserved. 13 | 14 | At the discretion of the user of this library, 15 | this software may be licensed under the terms of the 16 | GNU Public License v3, a BSD-Style license, or the 17 | original HIDAPI license as outlined in the LICENSE.txt, 18 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 19 | files located at the root of the source distribution. 20 | These files may also be found in the public source 21 | code repository located at: 22 | http://github.com/signal11/hidapi . 23 | ********************************************************/ 24 | 25 | #define _GNU_SOURCE // needed for wcsdup() before glibc 2.10 26 | 27 | /* C */ 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | /* Unix */ 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | /* GNU / LibUSB */ 46 | //#include "libusb.h" 47 | #include 48 | //#include "iconv.h" 49 | #include 50 | 51 | #include "hidapi.h" 52 | 53 | #ifdef __cplusplus 54 | extern "C" { 55 | #endif 56 | 57 | #ifdef DEBUG_PRINTF 58 | #define LOG(...) fprintf(stderr, __VA_ARGS__) 59 | #else 60 | #define LOG(...) do {} while (0) 61 | #endif 62 | 63 | 64 | /* Uncomment to enable the retrieval of Usage and Usage Page in 65 | hid_enumerate(). Warning, this is very invasive as it requires the detach 66 | and re-attach of the kernel driver. See comments inside hid_enumerate(). 67 | Linux/libusb HIDAPI programs are encouraged to use the interface number 68 | instead to differentiate between interfaces on a composite HID device. */ 69 | /*#define INVASIVE_GET_USAGE*/ 70 | 71 | /* Linked List of input reports received from the device. */ 72 | struct input_report { 73 | uint8_t *data; 74 | size_t len; 75 | struct input_report *next; 76 | }; 77 | 78 | 79 | struct hid_device_ { 80 | /* Handle to the actual device. */ 81 | libusb_device_handle *device_handle; 82 | 83 | /* Endpoint information */ 84 | int input_endpoint; 85 | int output_endpoint; 86 | int input_ep_max_packet_size; 87 | 88 | /* The interface number of the HID */ 89 | int interface; 90 | 91 | /* Indexes of Strings */ 92 | int manufacturer_index; 93 | int product_index; 94 | int serial_index; 95 | 96 | /* Whether blocking reads are used */ 97 | int blocking; /* boolean */ 98 | 99 | /* Read thread objects */ 100 | pthread_t thread; 101 | pthread_mutex_t mutex; /* Protects input_reports */ 102 | pthread_cond_t condition; 103 | pthread_barrier_t barrier; /* Ensures correct startup sequence */ 104 | int shutdown_thread; 105 | struct libusb_transfer *transfer; 106 | 107 | /* List of received input reports. */ 108 | struct input_report *input_reports; 109 | }; 110 | 111 | static int initialized = 0; 112 | 113 | uint16_t get_usb_code_for_current_locale(void); 114 | static int return_data(hid_device *dev, unsigned char *data, size_t length); 115 | 116 | static hid_device *new_hid_device(void) 117 | { 118 | hid_device *dev = calloc(1, sizeof(hid_device)); 119 | dev->device_handle = NULL; 120 | dev->input_endpoint = 0; 121 | dev->output_endpoint = 0; 122 | dev->input_ep_max_packet_size = 0; 123 | dev->interface = 0; 124 | dev->manufacturer_index = 0; 125 | dev->product_index = 0; 126 | dev->serial_index = 0; 127 | dev->blocking = 1; 128 | dev->shutdown_thread = 0; 129 | dev->transfer = NULL; 130 | dev->input_reports = NULL; 131 | 132 | pthread_mutex_init(&dev->mutex, NULL); 133 | pthread_cond_init(&dev->condition, NULL); 134 | pthread_barrier_init(&dev->barrier, NULL, 2); 135 | 136 | return dev; 137 | } 138 | 139 | static void free_hid_device(hid_device *dev) 140 | { 141 | /* Clean up the thread objects */ 142 | pthread_barrier_destroy(&dev->barrier); 143 | pthread_cond_destroy(&dev->condition); 144 | pthread_mutex_destroy(&dev->mutex); 145 | 146 | /* Free the device itself */ 147 | free(dev); 148 | } 149 | 150 | #if 0 151 | //TODO: Implement this funciton on Linux. 152 | static void register_error(hid_device *device, const char *op) 153 | { 154 | 155 | } 156 | #endif 157 | 158 | #ifdef INVASIVE_GET_USAGE 159 | /* Get bytes from a HID Report Descriptor. 160 | Only call with a num_bytes of 0, 1, 2, or 4. */ 161 | static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur) 162 | { 163 | /* Return if there aren't enough bytes. */ 164 | if (cur + num_bytes >= len) 165 | return 0; 166 | 167 | if (num_bytes == 0) 168 | return 0; 169 | else if (num_bytes == 1) { 170 | return rpt[cur+1]; 171 | } 172 | else if (num_bytes == 2) { 173 | return (rpt[cur+2] * 256 + rpt[cur+1]); 174 | } 175 | else if (num_bytes == 4) { 176 | return (rpt[cur+4] * 0x01000000 + 177 | rpt[cur+3] * 0x00010000 + 178 | rpt[cur+2] * 0x00000100 + 179 | rpt[cur+1] * 0x00000001); 180 | } 181 | else 182 | return 0; 183 | } 184 | 185 | /* Retrieves the device's Usage Page and Usage from the report 186 | descriptor. The algorithm is simple, as it just returns the first 187 | Usage and Usage Page that it finds in the descriptor. 188 | The return value is 0 on success and -1 on failure. */ 189 | static int get_usage(uint8_t *report_descriptor, size_t size, 190 | unsigned short *usage_page, unsigned short *usage) 191 | { 192 | int i = 0; 193 | int size_code; 194 | int data_len, key_size; 195 | int usage_found = 0, usage_page_found = 0; 196 | 197 | while (i < size) { 198 | int key = report_descriptor[i]; 199 | int key_cmd = key & 0xfc; 200 | 201 | //printf("key: %02hhx\n", key); 202 | 203 | if ((key & 0xf0) == 0xf0) { 204 | /* This is a Long Item. The next byte contains the 205 | length of the data section (value) for this key. 206 | See the HID specification, version 1.11, section 207 | 6.2.2.3, titled "Long Items." */ 208 | if (i+1 < size) 209 | data_len = report_descriptor[i+1]; 210 | else 211 | data_len = 0; /* malformed report */ 212 | key_size = 3; 213 | } 214 | else { 215 | /* This is a Short Item. The bottom two bits of the 216 | key contain the size code for the data section 217 | (value) for this key. Refer to the HID 218 | specification, version 1.11, section 6.2.2.2, 219 | titled "Short Items." */ 220 | size_code = key & 0x3; 221 | switch (size_code) { 222 | case 0: 223 | case 1: 224 | case 2: 225 | data_len = size_code; 226 | break; 227 | case 3: 228 | data_len = 4; 229 | break; 230 | default: 231 | /* Can't ever happen since size_code is & 0x3 */ 232 | data_len = 0; 233 | break; 234 | }; 235 | key_size = 1; 236 | } 237 | 238 | if (key_cmd == 0x4) { 239 | *usage_page = get_bytes(report_descriptor, size, data_len, i); 240 | usage_page_found = 1; 241 | //printf("Usage Page: %x\n", (uint32_t)*usage_page); 242 | } 243 | if (key_cmd == 0x8) { 244 | *usage = get_bytes(report_descriptor, size, data_len, i); 245 | usage_found = 1; 246 | //printf("Usage: %x\n", (uint32_t)*usage); 247 | } 248 | 249 | if (usage_page_found && usage_found) 250 | return 0; /* success */ 251 | 252 | /* Skip over this key and it's associated data */ 253 | i += data_len + key_size; 254 | } 255 | 256 | return -1; /* failure */ 257 | } 258 | #endif // INVASIVE_GET_USAGE 259 | 260 | 261 | /* Get the first language the device says it reports. This comes from 262 | USB string #0. */ 263 | static uint16_t get_first_language(libusb_device_handle *dev) 264 | { 265 | uint16_t buf[32]; 266 | int len; 267 | 268 | /* Get the string from libusb. */ 269 | len = libusb_get_string_descriptor(dev, 270 | 0x0, /* String ID */ 271 | 0x0, /* Language */ 272 | (unsigned char*)buf, 273 | sizeof(buf)); 274 | if (len < 4) 275 | return 0x0; 276 | 277 | return buf[1]; // First two bytes are len and descriptor type. 278 | } 279 | 280 | static int is_language_supported(libusb_device_handle *dev, uint16_t lang) 281 | { 282 | uint16_t buf[32]; 283 | int len; 284 | int i; 285 | 286 | /* Get the string from libusb. */ 287 | len = libusb_get_string_descriptor(dev, 288 | 0x0, /* String ID */ 289 | 0x0, /* Language */ 290 | (unsigned char*)buf, 291 | sizeof(buf)); 292 | if (len < 4) 293 | return 0x0; 294 | 295 | 296 | len /= 2; /* language IDs are two-bytes each. */ 297 | /* Start at index 1 because there are two bytes of protocol data. */ 298 | for (i = 1; i < len; i++) { 299 | if (buf[i] == lang) 300 | return 1; 301 | } 302 | 303 | return 0; 304 | } 305 | 306 | 307 | /* This function returns a newly allocated wide string containing the USB 308 | device string numbered by the index. The returned string must be freed 309 | by using free(). */ 310 | static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) 311 | { 312 | char buf[512]; 313 | int len; 314 | wchar_t *str = NULL; 315 | wchar_t wbuf[256]; 316 | 317 | /* iconv variables */ 318 | iconv_t ic; 319 | size_t inbytes; 320 | size_t outbytes; 321 | size_t res; 322 | char *inptr; 323 | char *outptr; 324 | 325 | /* Determine which language to use. */ 326 | uint16_t lang; 327 | lang = get_usb_code_for_current_locale(); 328 | if (!is_language_supported(dev, lang)) 329 | lang = get_first_language(dev); 330 | 331 | /* Get the string from libusb. */ 332 | len = libusb_get_string_descriptor(dev, 333 | idx, 334 | lang, 335 | (unsigned char*)buf, 336 | sizeof(buf)); 337 | if (len < 0) 338 | return NULL; 339 | 340 | buf[sizeof(buf)-1] = '\0'; 341 | 342 | if (len+1 < sizeof(buf)) 343 | buf[len+1] = '\0'; 344 | 345 | /* Initialize iconv. */ 346 | ic = iconv_open("UTF-32", "UTF-16"); 347 | if (ic == (iconv_t)-1) 348 | return NULL; 349 | 350 | /* Convert to UTF-32 (wchar_t on glibc systems). 351 | Skip the first character (2-bytes). */ 352 | inptr = buf+2; 353 | inbytes = len-2; 354 | outptr = (char*) wbuf; 355 | outbytes = sizeof(wbuf); 356 | res = iconv(ic, &inptr, &inbytes, &outptr, &outbytes); 357 | if (res == (size_t)-1) 358 | goto err; 359 | 360 | /* Write the terminating NULL. */ 361 | wbuf[sizeof(wbuf)/sizeof(wbuf[0])-1] = 0x00000000; 362 | if (outbytes >= sizeof(wbuf[0])) 363 | *((wchar_t*)outptr) = 0x00000000; 364 | 365 | /* Allocate and copy the string. */ 366 | str = wcsdup(wbuf+1); 367 | 368 | err: 369 | iconv_close(ic); 370 | 371 | return str; 372 | } 373 | 374 | static char *make_path(libusb_device *dev, int interface_number) 375 | { 376 | char str[64]; 377 | snprintf(str, sizeof(str), "%04x:%04x:%02x", 378 | libusb_get_bus_number(dev), 379 | libusb_get_device_address(dev), 380 | interface_number); 381 | str[sizeof(str)-1] = '\0'; 382 | 383 | return strdup(str); 384 | } 385 | 386 | 387 | int HID_API_EXPORT hid_init(void) 388 | { 389 | if (!initialized) { 390 | if (libusb_init(NULL)) 391 | return -1; 392 | initialized = 1; 393 | } 394 | 395 | return 0; 396 | } 397 | 398 | int HID_API_EXPORT hid_exit(void) 399 | { 400 | if (initialized) { 401 | libusb_exit(NULL); 402 | initialized = 0; 403 | } 404 | 405 | return 0; 406 | } 407 | 408 | struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) 409 | { 410 | libusb_device **devs; 411 | libusb_device *dev; 412 | libusb_device_handle *handle; 413 | ssize_t num_devs; 414 | int i = 0; 415 | 416 | struct hid_device_info *root = NULL; // return object 417 | struct hid_device_info *cur_dev = NULL; 418 | 419 | setlocale(LC_ALL,""); 420 | 421 | if (!initialized) 422 | hid_init(); 423 | 424 | num_devs = libusb_get_device_list(NULL, &devs); 425 | if (num_devs < 0) 426 | return NULL; 427 | while ((dev = devs[i++]) != NULL) { 428 | struct libusb_device_descriptor desc; 429 | struct libusb_config_descriptor *conf_desc = NULL; 430 | int j, k; 431 | int interface_num = 0; 432 | 433 | int res = libusb_get_device_descriptor(dev, &desc); 434 | unsigned short dev_vid = desc.idVendor; 435 | unsigned short dev_pid = desc.idProduct; 436 | 437 | /* HID's are defined at the interface level. */ 438 | if (desc.bDeviceClass != LIBUSB_CLASS_PER_INTERFACE) 439 | continue; 440 | 441 | res = libusb_get_active_config_descriptor(dev, &conf_desc); 442 | if (res < 0) 443 | libusb_get_config_descriptor(dev, 0, &conf_desc); 444 | if (conf_desc) { 445 | for (j = 0; j < conf_desc->bNumInterfaces; j++) { 446 | const struct libusb_interface *intf = &conf_desc->interface[j]; 447 | for (k = 0; k < intf->num_altsetting; k++) { 448 | const struct libusb_interface_descriptor *intf_desc; 449 | intf_desc = &intf->altsetting[k]; 450 | if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { 451 | interface_num = intf_desc->bInterfaceNumber; 452 | 453 | /* Check the VID/PID against the arguments */ 454 | if ((vendor_id == 0x0 && product_id == 0x0) || 455 | (vendor_id == dev_vid && product_id == dev_pid)) { 456 | struct hid_device_info *tmp; 457 | 458 | /* VID/PID match. Create the record. */ 459 | tmp = calloc(1, sizeof(struct hid_device_info)); 460 | if (cur_dev) { 461 | cur_dev->next = tmp; 462 | } 463 | else { 464 | root = tmp; 465 | } 466 | cur_dev = tmp; 467 | 468 | /* Fill out the record */ 469 | cur_dev->next = NULL; 470 | cur_dev->path = make_path(dev, interface_num); 471 | 472 | res = libusb_open(dev, &handle); 473 | 474 | if (res >= 0) { 475 | /* Serial Number */ 476 | if (desc.iSerialNumber > 0) 477 | cur_dev->serial_number = 478 | get_usb_string(handle, desc.iSerialNumber); 479 | 480 | /* Manufacturer and Product strings */ 481 | if (desc.iManufacturer > 0) 482 | cur_dev->manufacturer_string = 483 | get_usb_string(handle, desc.iManufacturer); 484 | if (desc.iProduct > 0) 485 | cur_dev->product_string = 486 | get_usb_string(handle, desc.iProduct); 487 | 488 | #ifdef INVASIVE_GET_USAGE 489 | /* 490 | This section is removed because it is too 491 | invasive on the system. Getting a Usage Page 492 | and Usage requires parsing the HID Report 493 | descriptor. Getting a HID Report descriptor 494 | involves claiming the interface. Claiming the 495 | interface involves detaching the kernel driver. 496 | Detaching the kernel driver is hard on the system 497 | because it will unclaim interfaces (if another 498 | app has them claimed) and the re-attachment of 499 | the driver will sometimes change /dev entry names. 500 | It is for these reasons that this section is 501 | #if 0. For composite devices, use the interface 502 | field in the hid_device_info struct to distinguish 503 | between interfaces. */ 504 | int detached = 0; 505 | unsigned char data[256]; 506 | 507 | /* Usage Page and Usage */ 508 | res = libusb_kernel_driver_active(handle, interface_num); 509 | if (res == 1) { 510 | res = libusb_detach_kernel_driver(handle, interface_num); 511 | if (res < 0) 512 | LOG("Couldn't detach kernel driver, even though a kernel driver was attached."); 513 | else 514 | detached = 1; 515 | } 516 | res = libusb_claim_interface(handle, interface_num); 517 | if (res >= 0) { 518 | /* Get the HID Report Descriptor. */ 519 | res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8)|interface_num, 0, data, sizeof(data), 5000); 520 | if (res >= 0) { 521 | unsigned short page=0, usage=0; 522 | /* Parse the usage and usage page 523 | out of the report descriptor. */ 524 | get_usage(data, res, &page, &usage); 525 | cur_dev->usage_page = page; 526 | cur_dev->usage = usage; 527 | } 528 | else 529 | LOG("libusb_control_transfer() for getting the HID report failed with %d\n", res); 530 | 531 | /* Release the interface */ 532 | res = libusb_release_interface(handle, interface_num); 533 | if (res < 0) 534 | LOG("Can't release the interface.\n"); 535 | } 536 | else 537 | LOG("Can't claim interface %d\n", res); 538 | 539 | /* Re-attach kernel driver if necessary. */ 540 | if (detached) { 541 | res = libusb_attach_kernel_driver(handle, interface_num); 542 | if (res < 0) 543 | LOG("Couldn't re-attach kernel driver.\n"); 544 | } 545 | #endif /*******************/ 546 | 547 | libusb_close(handle); 548 | } 549 | /* VID/PID */ 550 | cur_dev->vendor_id = dev_vid; 551 | cur_dev->product_id = dev_pid; 552 | 553 | /* Release Number */ 554 | cur_dev->release_number = desc.bcdDevice; 555 | 556 | /* Interface Number */ 557 | cur_dev->interface_number = interface_num; 558 | } 559 | } 560 | } /* altsettings */ 561 | } /* interfaces */ 562 | libusb_free_config_descriptor(conf_desc); 563 | } 564 | } 565 | 566 | libusb_free_device_list(devs, 1); 567 | 568 | return root; 569 | } 570 | 571 | void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) 572 | { 573 | struct hid_device_info *d = devs; 574 | while (d) { 575 | struct hid_device_info *next = d->next; 576 | free(d->path); 577 | free(d->serial_number); 578 | free(d->manufacturer_string); 579 | free(d->product_string); 580 | free(d); 581 | d = next; 582 | } 583 | } 584 | 585 | hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, wchar_t *serial_number) 586 | { 587 | struct hid_device_info *devs, *cur_dev; 588 | const char *path_to_open = NULL; 589 | hid_device *handle = NULL; 590 | 591 | devs = hid_enumerate(vendor_id, product_id); 592 | cur_dev = devs; 593 | while (cur_dev) { 594 | if (cur_dev->vendor_id == vendor_id && 595 | cur_dev->product_id == product_id) { 596 | if (serial_number) { 597 | if (wcscmp(serial_number, cur_dev->serial_number) == 0) { 598 | path_to_open = cur_dev->path; 599 | break; 600 | } 601 | } 602 | else { 603 | path_to_open = cur_dev->path; 604 | break; 605 | } 606 | } 607 | cur_dev = cur_dev->next; 608 | } 609 | 610 | if (path_to_open) { 611 | /* Open the device */ 612 | handle = hid_open_path(path_to_open); 613 | } 614 | 615 | hid_free_enumeration(devs); 616 | 617 | return handle; 618 | } 619 | 620 | static void read_callback(struct libusb_transfer *transfer) 621 | { 622 | hid_device *dev = transfer->user_data; 623 | 624 | if (transfer->status == LIBUSB_TRANSFER_COMPLETED) { 625 | 626 | struct input_report *rpt = malloc(sizeof(*rpt)); 627 | rpt->data = malloc(transfer->actual_length); 628 | memcpy(rpt->data, transfer->buffer, transfer->actual_length); 629 | rpt->len = transfer->actual_length; 630 | rpt->next = NULL; 631 | 632 | pthread_mutex_lock(&dev->mutex); 633 | 634 | /* Attach the new report object to the end of the list. */ 635 | if (dev->input_reports == NULL) { 636 | /* The list is empty. Put it at the root. */ 637 | dev->input_reports = rpt; 638 | pthread_cond_signal(&dev->condition); 639 | } 640 | else { 641 | /* Find the end of the list and attach. */ 642 | struct input_report *cur = dev->input_reports; 643 | int num_queued = 0; 644 | while (cur->next != NULL) { 645 | cur = cur->next; 646 | num_queued++; 647 | } 648 | cur->next = rpt; 649 | 650 | /* Pop one off if we've reached 30 in the queue. This 651 | way we don't grow forever if the user never reads 652 | anything from the device. */ 653 | if (num_queued > 30) { 654 | return_data(dev, NULL, 0); 655 | } 656 | } 657 | pthread_mutex_unlock(&dev->mutex); 658 | } 659 | else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) { 660 | dev->shutdown_thread = 1; 661 | return; 662 | } 663 | else if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE) { 664 | dev->shutdown_thread = 1; 665 | return; 666 | } 667 | else if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) { 668 | //LOG("Timeout (normal)\n"); 669 | } 670 | else { 671 | LOG("Unknown transfer code: %d\n", transfer->status); 672 | } 673 | 674 | /* Re-submit the transfer object. */ 675 | libusb_submit_transfer(transfer); 676 | } 677 | 678 | 679 | static void *read_thread(void *param) 680 | { 681 | hid_device *dev = param; 682 | unsigned char *buf; 683 | const size_t length = dev->input_ep_max_packet_size; 684 | 685 | /* Set up the transfer object. */ 686 | buf = malloc(length); 687 | dev->transfer = libusb_alloc_transfer(0); 688 | libusb_fill_interrupt_transfer(dev->transfer, 689 | dev->device_handle, 690 | dev->input_endpoint, 691 | buf, 692 | length, 693 | read_callback, 694 | dev, 695 | 5000/*timeout*/); 696 | 697 | /* Make the first submission. Further submissions are made 698 | from inside read_callback() */ 699 | libusb_submit_transfer(dev->transfer); 700 | 701 | // Notify the main thread that the read thread is up and running. 702 | pthread_barrier_wait(&dev->barrier); 703 | 704 | /* Handle all the events. */ 705 | while (!dev->shutdown_thread) { 706 | int res; 707 | res = libusb_handle_events(NULL); 708 | if (res < 0) { 709 | /* There was an error. Break out of this loop. */ 710 | break; 711 | } 712 | } 713 | 714 | /* Cancel any transfer that may be pending. This call will fail 715 | if no transfers are pending, but that's OK. */ 716 | if (libusb_cancel_transfer(dev->transfer) == 0) { 717 | /* The transfer was cancelled, so wait for its completion. */ 718 | libusb_handle_events(NULL); 719 | } 720 | 721 | /* Now that the read thread is stopping, Wake any threads which are 722 | waiting on data (in hid_read_timeout()). Do this under a mutex to 723 | make sure that a thread which is about to go to sleep waiting on 724 | the condition acutally will go to sleep before the condition is 725 | signaled. */ 726 | pthread_mutex_lock(&dev->mutex); 727 | pthread_cond_broadcast(&dev->condition); 728 | pthread_mutex_unlock(&dev->mutex); 729 | 730 | /* The dev->transfer->buffer and dev->transfer objects are cleaned up 731 | in hid_close(). They are not cleaned up here because this thread 732 | could end either due to a disconnect or due to a user 733 | call to hid_close(). In both cases the objects can be safely 734 | cleaned up after the call to pthread_join() (in hid_close()), but 735 | since hid_close() calls libusb_cancel_transfer(), on these objects, 736 | they can not be cleaned up here. */ 737 | 738 | return NULL; 739 | } 740 | 741 | 742 | hid_device * HID_API_EXPORT hid_open_path(const char *path) 743 | { 744 | hid_device *dev = NULL; 745 | 746 | dev = new_hid_device(); 747 | 748 | libusb_device **devs; 749 | libusb_device *usb_dev; 750 | ssize_t num_devs; 751 | int res; 752 | int d = 0; 753 | int good_open = 0; 754 | 755 | setlocale(LC_ALL,""); 756 | 757 | if (!initialized) 758 | hid_init(); 759 | 760 | num_devs = libusb_get_device_list(NULL, &devs); 761 | while ((usb_dev = devs[d++]) != NULL) { 762 | struct libusb_device_descriptor desc; 763 | struct libusb_config_descriptor *conf_desc = NULL; 764 | int i,j,k; 765 | libusb_get_device_descriptor(usb_dev, &desc); 766 | 767 | if (libusb_get_active_config_descriptor(usb_dev, &conf_desc) < 0) 768 | continue; 769 | for (j = 0; j < conf_desc->bNumInterfaces; j++) { 770 | const struct libusb_interface *intf = &conf_desc->interface[j]; 771 | for (k = 0; k < intf->num_altsetting; k++) { 772 | const struct libusb_interface_descriptor *intf_desc; 773 | intf_desc = &intf->altsetting[k]; 774 | if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { 775 | char *dev_path = make_path(usb_dev, intf_desc->bInterfaceNumber); 776 | if (!strcmp(dev_path, path)) { 777 | /* Matched Paths. Open this device */ 778 | 779 | // OPEN HERE // 780 | res = libusb_open(usb_dev, &dev->device_handle); 781 | if (res < 0) { 782 | LOG("can't open device\n"); 783 | free(dev_path); 784 | break; 785 | } 786 | good_open = 1; 787 | 788 | /* Detach the kernel driver, but only if the 789 | device is managed by the kernel */ 790 | if (libusb_kernel_driver_active(dev->device_handle, intf_desc->bInterfaceNumber) == 1) { 791 | res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); 792 | if (res < 0) { 793 | libusb_close(dev->device_handle); 794 | LOG("Unable to detach Kernel Driver\n"); 795 | free(dev_path); 796 | good_open = 0; 797 | break; 798 | } 799 | } 800 | 801 | res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber); 802 | if (res < 0) { 803 | LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res); 804 | free(dev_path); 805 | libusb_close(dev->device_handle); 806 | good_open = 0; 807 | break; 808 | } 809 | 810 | /* Store off the string descriptor indexes */ 811 | dev->manufacturer_index = desc.iManufacturer; 812 | dev->product_index = desc.iProduct; 813 | dev->serial_index = desc.iSerialNumber; 814 | 815 | /* Store off the interface number */ 816 | dev->interface = intf_desc->bInterfaceNumber; 817 | 818 | /* Find the INPUT and OUTPUT endpoints. An 819 | OUTPUT endpoint is not required. */ 820 | for (i = 0; i < intf_desc->bNumEndpoints; i++) { 821 | const struct libusb_endpoint_descriptor *ep 822 | = &intf_desc->endpoint[i]; 823 | 824 | /* Determine the type and direction of this 825 | endpoint. */ 826 | int is_interrupt = 827 | (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) 828 | == LIBUSB_TRANSFER_TYPE_INTERRUPT; 829 | int is_output = 830 | (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) 831 | == LIBUSB_ENDPOINT_OUT; 832 | int is_input = 833 | (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) 834 | == LIBUSB_ENDPOINT_IN; 835 | 836 | /* Decide whether to use it for intput or output. */ 837 | if (dev->input_endpoint == 0 && 838 | is_interrupt && is_input) { 839 | /* Use this endpoint for INPUT */ 840 | dev->input_endpoint = ep->bEndpointAddress; 841 | dev->input_ep_max_packet_size = ep->wMaxPacketSize; 842 | } 843 | if (dev->output_endpoint == 0 && 844 | is_interrupt && is_output) { 845 | /* Use this endpoint for OUTPUT */ 846 | dev->output_endpoint = ep->bEndpointAddress; 847 | } 848 | } 849 | 850 | pthread_create(&dev->thread, NULL, read_thread, dev); 851 | 852 | // Wait here for the read thread to be initialized. 853 | pthread_barrier_wait(&dev->barrier); 854 | 855 | } 856 | free(dev_path); 857 | } 858 | } 859 | } 860 | libusb_free_config_descriptor(conf_desc); 861 | 862 | } 863 | 864 | libusb_free_device_list(devs, 1); 865 | 866 | // If we have a good handle, return it. 867 | if (good_open) { 868 | return dev; 869 | } 870 | else { 871 | // Unable to open any devices. 872 | free_hid_device(dev); 873 | return NULL; 874 | } 875 | } 876 | 877 | 878 | int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) 879 | { 880 | int res; 881 | int report_number = data[0]; 882 | int skipped_report_id = 0; 883 | 884 | if (report_number == 0x0) { 885 | data++; 886 | length--; 887 | skipped_report_id = 1; 888 | } 889 | 890 | 891 | if (dev->output_endpoint <= 0) { 892 | /* No interrput out endpoint. Use the Control Endpoint */ 893 | res = libusb_control_transfer(dev->device_handle, 894 | LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 895 | 0x09/*HID Set_Report*/, 896 | (2/*HID output*/ << 8) | report_number, 897 | dev->interface, 898 | (unsigned char *)data, length, 899 | 1000/*timeout millis*/); 900 | 901 | if (res < 0) 902 | return -1; 903 | 904 | if (skipped_report_id) 905 | length++; 906 | 907 | return length; 908 | } 909 | else { 910 | /* Use the interrupt out endpoint */ 911 | int actual_length; 912 | res = libusb_interrupt_transfer(dev->device_handle, 913 | dev->output_endpoint, 914 | (unsigned char*)data, 915 | length, 916 | &actual_length, 1000); 917 | 918 | if (res < 0) 919 | return -1; 920 | 921 | if (skipped_report_id) 922 | actual_length++; 923 | 924 | return actual_length; 925 | } 926 | } 927 | 928 | /* Helper function, to simplify hid_read(). 929 | This should be called with dev->mutex locked. */ 930 | static int return_data(hid_device *dev, unsigned char *data, size_t length) 931 | { 932 | /* Copy the data out of the linked list item (rpt) into the 933 | return buffer (data), and delete the liked list item. */ 934 | struct input_report *rpt = dev->input_reports; 935 | size_t len = (length < rpt->len)? length: rpt->len; 936 | if (len > 0) 937 | memcpy(data, rpt->data, len); 938 | dev->input_reports = rpt->next; 939 | free(rpt->data); 940 | free(rpt); 941 | return len; 942 | } 943 | 944 | static void cleanup_mutex(void *param) 945 | { 946 | hid_device *dev = param; 947 | pthread_mutex_unlock(&dev->mutex); 948 | } 949 | 950 | 951 | int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) 952 | { 953 | int bytes_read = -1; 954 | 955 | #if 0 956 | int transferred; 957 | int res = libusb_interrupt_transfer(dev->device_handle, dev->input_endpoint, data, length, &transferred, 5000); 958 | LOG("transferred: %d\n", transferred); 959 | return transferred; 960 | #endif 961 | 962 | pthread_mutex_lock(&dev->mutex); 963 | pthread_cleanup_push(&cleanup_mutex, dev); 964 | 965 | /* There's an input report queued up. Return it. */ 966 | if (dev->input_reports) { 967 | /* Return the first one */ 968 | bytes_read = return_data(dev, data, length); 969 | goto ret; 970 | } 971 | 972 | if (dev->shutdown_thread) { 973 | /* This means the device has been disconnected. 974 | An error code of -1 should be returned. */ 975 | bytes_read = -1; 976 | goto ret; 977 | } 978 | 979 | if (milliseconds == -1) { 980 | /* Blocking */ 981 | while (!dev->input_reports && !dev->shutdown_thread) { 982 | pthread_cond_wait(&dev->condition, &dev->mutex); 983 | } 984 | if (dev->input_reports) { 985 | bytes_read = return_data(dev, data, length); 986 | } 987 | } 988 | else if (milliseconds > 0) { 989 | /* Non-blocking, but called with timeout. */ 990 | int res; 991 | struct timespec ts; 992 | clock_gettime(CLOCK_REALTIME, &ts); 993 | ts.tv_sec += milliseconds / 1000; 994 | ts.tv_nsec += (milliseconds % 1000) * 1000000; 995 | if (ts.tv_nsec >= 1000000000L) { 996 | ts.tv_sec++; 997 | ts.tv_nsec -= 1000000000L; 998 | } 999 | 1000 | while (!dev->input_reports && !dev->shutdown_thread) { 1001 | res = pthread_cond_timedwait(&dev->condition, &dev->mutex, &ts); 1002 | if (res == 0) { 1003 | if (dev->input_reports) { 1004 | bytes_read = return_data(dev, data, length); 1005 | break; 1006 | } 1007 | 1008 | /* If we're here, there was a spurious wake up 1009 | or the read thread was shutdown. Run the 1010 | loop again (ie: don't break). */ 1011 | } 1012 | else if (res == ETIMEDOUT) { 1013 | /* Timed out. */ 1014 | bytes_read = 0; 1015 | break; 1016 | } 1017 | else { 1018 | /* Error. */ 1019 | bytes_read = -1; 1020 | break; 1021 | } 1022 | } 1023 | } 1024 | else { 1025 | /* Purely non-blocking */ 1026 | bytes_read = 0; 1027 | } 1028 | 1029 | ret: 1030 | pthread_mutex_unlock(&dev->mutex); 1031 | pthread_cleanup_pop(0); 1032 | 1033 | return bytes_read; 1034 | } 1035 | 1036 | int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) 1037 | { 1038 | return hid_read_timeout(dev, data, length, dev->blocking ? -1 : 0); 1039 | } 1040 | 1041 | int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) 1042 | { 1043 | dev->blocking = !nonblock; 1044 | 1045 | return 0; 1046 | } 1047 | 1048 | 1049 | int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) 1050 | { 1051 | int res = -1; 1052 | int skipped_report_id = 0; 1053 | int report_number = data[0]; 1054 | 1055 | if (report_number == 0x0) { 1056 | data++; 1057 | length--; 1058 | skipped_report_id = 1; 1059 | } 1060 | 1061 | res = libusb_control_transfer(dev->device_handle, 1062 | LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 1063 | 0x09/*HID set_report*/, 1064 | (3/*HID feature*/ << 8) | report_number, 1065 | dev->interface, 1066 | (unsigned char *)data, length, 1067 | 1000/*timeout millis*/); 1068 | 1069 | if (res < 0) 1070 | return -1; 1071 | 1072 | /* Account for the report ID */ 1073 | if (skipped_report_id) 1074 | length++; 1075 | 1076 | return length; 1077 | } 1078 | 1079 | int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) 1080 | { 1081 | int res = -1; 1082 | int skipped_report_id = 0; 1083 | int report_number = data[0]; 1084 | 1085 | if (report_number == 0x0) { 1086 | /* Offset the return buffer by 1, so that the report ID 1087 | will remain in byte 0. */ 1088 | data++; 1089 | length--; 1090 | skipped_report_id = 1; 1091 | } 1092 | res = libusb_control_transfer(dev->device_handle, 1093 | LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, 1094 | 0x01/*HID get_report*/, 1095 | (3/*HID feature*/ << 8) | report_number, 1096 | dev->interface, 1097 | (unsigned char *)data, length, 1098 | 1000/*timeout millis*/); 1099 | 1100 | if (res < 0) 1101 | return -1; 1102 | 1103 | if (skipped_report_id) 1104 | res++; 1105 | 1106 | return res; 1107 | } 1108 | 1109 | 1110 | void HID_API_EXPORT hid_close(hid_device *dev) 1111 | { 1112 | if (!dev) 1113 | return; 1114 | 1115 | /* Cause read_thread() to stop. */ 1116 | dev->shutdown_thread = 1; 1117 | libusb_cancel_transfer(dev->transfer); 1118 | 1119 | /* Wait for read_thread() to end. */ 1120 | pthread_join(dev->thread, NULL); 1121 | 1122 | /* Clean up the Transfer objects allocated in read_thread(). */ 1123 | free(dev->transfer->buffer); 1124 | libusb_free_transfer(dev->transfer); 1125 | 1126 | /* release the interface */ 1127 | libusb_release_interface(dev->device_handle, dev->interface); 1128 | 1129 | /* Close the handle */ 1130 | libusb_close(dev->device_handle); 1131 | 1132 | /* Clear out the queue of received reports. */ 1133 | pthread_mutex_lock(&dev->mutex); 1134 | while (dev->input_reports) { 1135 | return_data(dev, NULL, 0); 1136 | } 1137 | pthread_mutex_unlock(&dev->mutex); 1138 | 1139 | free_hid_device(dev); 1140 | } 1141 | 1142 | 1143 | int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) 1144 | { 1145 | return hid_get_indexed_string(dev, dev->manufacturer_index, string, maxlen); 1146 | } 1147 | 1148 | int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) 1149 | { 1150 | return hid_get_indexed_string(dev, dev->product_index, string, maxlen); 1151 | } 1152 | 1153 | int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) 1154 | { 1155 | return hid_get_indexed_string(dev, dev->serial_index, string, maxlen); 1156 | } 1157 | 1158 | int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) 1159 | { 1160 | wchar_t *str; 1161 | 1162 | str = get_usb_string(dev->device_handle, string_index); 1163 | if (str) { 1164 | wcsncpy(string, str, maxlen); 1165 | string[maxlen-1] = L'\0'; 1166 | free(str); 1167 | return 0; 1168 | } 1169 | else 1170 | return -1; 1171 | } 1172 | 1173 | 1174 | HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) 1175 | { 1176 | return NULL; 1177 | } 1178 | 1179 | 1180 | struct lang_map_entry { 1181 | const char *name; 1182 | const char *string_code; 1183 | uint16_t usb_code; 1184 | }; 1185 | 1186 | #define LANG(name,code,usb_code) { name, code, usb_code } 1187 | static struct lang_map_entry lang_map[] = { 1188 | LANG("Afrikaans", "af", 0x0436), 1189 | LANG("Albanian", "sq", 0x041C), 1190 | LANG("Arabic - United Arab Emirates", "ar_ae", 0x3801), 1191 | LANG("Arabic - Bahrain", "ar_bh", 0x3C01), 1192 | LANG("Arabic - Algeria", "ar_dz", 0x1401), 1193 | LANG("Arabic - Egypt", "ar_eg", 0x0C01), 1194 | LANG("Arabic - Iraq", "ar_iq", 0x0801), 1195 | LANG("Arabic - Jordan", "ar_jo", 0x2C01), 1196 | LANG("Arabic - Kuwait", "ar_kw", 0x3401), 1197 | LANG("Arabic - Lebanon", "ar_lb", 0x3001), 1198 | LANG("Arabic - Libya", "ar_ly", 0x1001), 1199 | LANG("Arabic - Morocco", "ar_ma", 0x1801), 1200 | LANG("Arabic - Oman", "ar_om", 0x2001), 1201 | LANG("Arabic - Qatar", "ar_qa", 0x4001), 1202 | LANG("Arabic - Saudi Arabia", "ar_sa", 0x0401), 1203 | LANG("Arabic - Syria", "ar_sy", 0x2801), 1204 | LANG("Arabic - Tunisia", "ar_tn", 0x1C01), 1205 | LANG("Arabic - Yemen", "ar_ye", 0x2401), 1206 | LANG("Armenian", "hy", 0x042B), 1207 | LANG("Azeri - Latin", "az_az", 0x042C), 1208 | LANG("Azeri - Cyrillic", "az_az", 0x082C), 1209 | LANG("Basque", "eu", 0x042D), 1210 | LANG("Belarusian", "be", 0x0423), 1211 | LANG("Bulgarian", "bg", 0x0402), 1212 | LANG("Catalan", "ca", 0x0403), 1213 | LANG("Chinese - China", "zh_cn", 0x0804), 1214 | LANG("Chinese - Hong Kong SAR", "zh_hk", 0x0C04), 1215 | LANG("Chinese - Macau SAR", "zh_mo", 0x1404), 1216 | LANG("Chinese - Singapore", "zh_sg", 0x1004), 1217 | LANG("Chinese - Taiwan", "zh_tw", 0x0404), 1218 | LANG("Croatian", "hr", 0x041A), 1219 | LANG("Czech", "cs", 0x0405), 1220 | LANG("Danish", "da", 0x0406), 1221 | LANG("Dutch - Netherlands", "nl_nl", 0x0413), 1222 | LANG("Dutch - Belgium", "nl_be", 0x0813), 1223 | LANG("English - Australia", "en_au", 0x0C09), 1224 | LANG("English - Belize", "en_bz", 0x2809), 1225 | LANG("English - Canada", "en_ca", 0x1009), 1226 | LANG("English - Caribbean", "en_cb", 0x2409), 1227 | LANG("English - Ireland", "en_ie", 0x1809), 1228 | LANG("English - Jamaica", "en_jm", 0x2009), 1229 | LANG("English - New Zealand", "en_nz", 0x1409), 1230 | LANG("English - Phillippines", "en_ph", 0x3409), 1231 | LANG("English - Southern Africa", "en_za", 0x1C09), 1232 | LANG("English - Trinidad", "en_tt", 0x2C09), 1233 | LANG("English - Great Britain", "en_gb", 0x0809), 1234 | LANG("English - United States", "en_us", 0x0409), 1235 | LANG("Estonian", "et", 0x0425), 1236 | LANG("Farsi", "fa", 0x0429), 1237 | LANG("Finnish", "fi", 0x040B), 1238 | LANG("Faroese", "fo", 0x0438), 1239 | LANG("French - France", "fr_fr", 0x040C), 1240 | LANG("French - Belgium", "fr_be", 0x080C), 1241 | LANG("French - Canada", "fr_ca", 0x0C0C), 1242 | LANG("French - Luxembourg", "fr_lu", 0x140C), 1243 | LANG("French - Switzerland", "fr_ch", 0x100C), 1244 | LANG("Gaelic - Ireland", "gd_ie", 0x083C), 1245 | LANG("Gaelic - Scotland", "gd", 0x043C), 1246 | LANG("German - Germany", "de_de", 0x0407), 1247 | LANG("German - Austria", "de_at", 0x0C07), 1248 | LANG("German - Liechtenstein", "de_li", 0x1407), 1249 | LANG("German - Luxembourg", "de_lu", 0x1007), 1250 | LANG("German - Switzerland", "de_ch", 0x0807), 1251 | LANG("Greek", "el", 0x0408), 1252 | LANG("Hebrew", "he", 0x040D), 1253 | LANG("Hindi", "hi", 0x0439), 1254 | LANG("Hungarian", "hu", 0x040E), 1255 | LANG("Icelandic", "is", 0x040F), 1256 | LANG("Indonesian", "id", 0x0421), 1257 | LANG("Italian - Italy", "it_it", 0x0410), 1258 | LANG("Italian - Switzerland", "it_ch", 0x0810), 1259 | LANG("Japanese", "ja", 0x0411), 1260 | LANG("Korean", "ko", 0x0412), 1261 | LANG("Latvian", "lv", 0x0426), 1262 | LANG("Lithuanian", "lt", 0x0427), 1263 | LANG("F.Y.R.O. Macedonia", "mk", 0x042F), 1264 | LANG("Malay - Malaysia", "ms_my", 0x043E), 1265 | LANG("Malay – Brunei", "ms_bn", 0x083E), 1266 | LANG("Maltese", "mt", 0x043A), 1267 | LANG("Marathi", "mr", 0x044E), 1268 | LANG("Norwegian - Bokml", "no_no", 0x0414), 1269 | LANG("Norwegian - Nynorsk", "no_no", 0x0814), 1270 | LANG("Polish", "pl", 0x0415), 1271 | LANG("Portuguese - Portugal", "pt_pt", 0x0816), 1272 | LANG("Portuguese - Brazil", "pt_br", 0x0416), 1273 | LANG("Raeto-Romance", "rm", 0x0417), 1274 | LANG("Romanian - Romania", "ro", 0x0418), 1275 | LANG("Romanian - Republic of Moldova", "ro_mo", 0x0818), 1276 | LANG("Russian", "ru", 0x0419), 1277 | LANG("Russian - Republic of Moldova", "ru_mo", 0x0819), 1278 | LANG("Sanskrit", "sa", 0x044F), 1279 | LANG("Serbian - Cyrillic", "sr_sp", 0x0C1A), 1280 | LANG("Serbian - Latin", "sr_sp", 0x081A), 1281 | LANG("Setsuana", "tn", 0x0432), 1282 | LANG("Slovenian", "sl", 0x0424), 1283 | LANG("Slovak", "sk", 0x041B), 1284 | LANG("Sorbian", "sb", 0x042E), 1285 | LANG("Spanish - Spain (Traditional)", "es_es", 0x040A), 1286 | LANG("Spanish - Argentina", "es_ar", 0x2C0A), 1287 | LANG("Spanish - Bolivia", "es_bo", 0x400A), 1288 | LANG("Spanish - Chile", "es_cl", 0x340A), 1289 | LANG("Spanish - Colombia", "es_co", 0x240A), 1290 | LANG("Spanish - Costa Rica", "es_cr", 0x140A), 1291 | LANG("Spanish - Dominican Republic", "es_do", 0x1C0A), 1292 | LANG("Spanish - Ecuador", "es_ec", 0x300A), 1293 | LANG("Spanish - Guatemala", "es_gt", 0x100A), 1294 | LANG("Spanish - Honduras", "es_hn", 0x480A), 1295 | LANG("Spanish - Mexico", "es_mx", 0x080A), 1296 | LANG("Spanish - Nicaragua", "es_ni", 0x4C0A), 1297 | LANG("Spanish - Panama", "es_pa", 0x180A), 1298 | LANG("Spanish - Peru", "es_pe", 0x280A), 1299 | LANG("Spanish - Puerto Rico", "es_pr", 0x500A), 1300 | LANG("Spanish - Paraguay", "es_py", 0x3C0A), 1301 | LANG("Spanish - El Salvador", "es_sv", 0x440A), 1302 | LANG("Spanish - Uruguay", "es_uy", 0x380A), 1303 | LANG("Spanish - Venezuela", "es_ve", 0x200A), 1304 | LANG("Southern Sotho", "st", 0x0430), 1305 | LANG("Swahili", "sw", 0x0441), 1306 | LANG("Swedish - Sweden", "sv_se", 0x041D), 1307 | LANG("Swedish - Finland", "sv_fi", 0x081D), 1308 | LANG("Tamil", "ta", 0x0449), 1309 | LANG("Tatar", "tt", 0X0444), 1310 | LANG("Thai", "th", 0x041E), 1311 | LANG("Turkish", "tr", 0x041F), 1312 | LANG("Tsonga", "ts", 0x0431), 1313 | LANG("Ukrainian", "uk", 0x0422), 1314 | LANG("Urdu", "ur", 0x0420), 1315 | LANG("Uzbek - Cyrillic", "uz_uz", 0x0843), 1316 | LANG("Uzbek – Latin", "uz_uz", 0x0443), 1317 | LANG("Vietnamese", "vi", 0x042A), 1318 | LANG("Xhosa", "xh", 0x0434), 1319 | LANG("Yiddish", "yi", 0x043D), 1320 | LANG("Zulu", "zu", 0x0435), 1321 | LANG(NULL, NULL, 0x0), 1322 | }; 1323 | 1324 | uint16_t get_usb_code_for_current_locale(void) 1325 | { 1326 | char *locale; 1327 | char search_string[64]; 1328 | char *ptr; 1329 | 1330 | /* Get the current locale. */ 1331 | locale = setlocale(0, NULL); 1332 | if (!locale) 1333 | return 0x0; 1334 | 1335 | /* Make a copy of the current locale string. */ 1336 | strncpy(search_string, locale, sizeof(search_string)); 1337 | search_string[sizeof(search_string)-1] = '\0'; 1338 | 1339 | /* Chop off the encoding part, and make it lower case. */ 1340 | ptr = search_string; 1341 | while (*ptr) { 1342 | *ptr = tolower(*ptr); 1343 | if (*ptr == '.') { 1344 | *ptr = '\0'; 1345 | break; 1346 | } 1347 | ptr++; 1348 | } 1349 | 1350 | /* Find the entry which matches the string code of our locale. */ 1351 | struct lang_map_entry *lang = lang_map; 1352 | while (lang->string_code) { 1353 | if (!strcmp(lang->string_code, search_string)) { 1354 | return lang->usb_code; 1355 | } 1356 | lang++; 1357 | } 1358 | 1359 | /* There was no match. Find with just the language only. */ 1360 | /* Chop off the variant. Chop it off at the '_'. */ 1361 | ptr = search_string; 1362 | while (*ptr) { 1363 | *ptr = tolower(*ptr); 1364 | if (*ptr == '_') { 1365 | *ptr = '\0'; 1366 | break; 1367 | } 1368 | ptr++; 1369 | } 1370 | 1371 | #if 0 // TODO: Do we need this? 1372 | /* Find the entry which matches the string code of our language. */ 1373 | lang = lang_map; 1374 | while (lang->string_code) { 1375 | if (!strcmp(lang->string_code, search_string)) { 1376 | return lang->usb_code; 1377 | } 1378 | lang++; 1379 | } 1380 | #endif 1381 | 1382 | /* Found nothing. */ 1383 | return 0x0; 1384 | } 1385 | 1386 | #ifdef __cplusplus 1387 | } 1388 | #endif 1389 | -------------------------------------------------------------------------------- /hidapi.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | Alan Ott 6 | Signal 11 Software 7 | 8 | 8/22/2009 9 | 10 | Copyright 2009, All Rights Reserved. 11 | 12 | At the discretion of the user of this library, 13 | this software may be licensed under the terms of the 14 | GNU Public License v3, a BSD-Style license, or the 15 | original HIDAPI license as outlined in the LICENSE.txt, 16 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 17 | files located at the root of the source distribution. 18 | These files may also be found in the public source 19 | code repository located at: 20 | http://github.com/signal11/hidapi . 21 | ********************************************************/ 22 | 23 | /** @file 24 | * @defgroup API hidapi API 25 | */ 26 | 27 | #ifndef HIDAPI_H__ 28 | #define HIDAPI_H__ 29 | 30 | #include 31 | 32 | #ifdef _WIN32 33 | #define HID_API_EXPORT __declspec(dllexport) 34 | #define HID_API_CALL 35 | #else 36 | #define HID_API_EXPORT /**< API export macro */ 37 | #define HID_API_CALL /**< API call macro */ 38 | #endif 39 | 40 | #define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | struct hid_device_; 46 | typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ 47 | 48 | /** hidapi info structure */ 49 | struct hid_device_info { 50 | /** Platform-specific device path */ 51 | char *path; 52 | /** Device Vendor ID */ 53 | unsigned short vendor_id; 54 | /** Device Product ID */ 55 | unsigned short product_id; 56 | /** Serial Number */ 57 | wchar_t *serial_number; 58 | /** Device Release Number in binary-coded decimal, 59 | also known as Device Version Number */ 60 | unsigned short release_number; 61 | /** Manufacturer String */ 62 | wchar_t *manufacturer_string; 63 | /** Product string */ 64 | wchar_t *product_string; 65 | /** Usage Page for this Device/Interface 66 | (Windows/Mac only). */ 67 | unsigned short usage_page; 68 | /** Usage for this Device/Interface 69 | (Windows/Mac only).*/ 70 | unsigned short usage; 71 | /** The USB interface which this logical device 72 | represents. Valid on both Linux implementations 73 | in all cases, and valid on the Windows implementation 74 | only if the device contains more than one interface. */ 75 | int interface_number; 76 | 77 | /** Pointer to the next device */ 78 | struct hid_device_info *next; 79 | }; 80 | 81 | 82 | /** @brief Initialize the HIDAPI library. 83 | 84 | This function initializes the HIDAPI library. Calling it is not 85 | strictly necessary, as it will be called automatically by 86 | hid_enumerate() and any of the hid_open_*() functions if it is 87 | needed. This function should be called at the beginning of 88 | execution however, if there is a chance of HIDAPI handles 89 | being opened by different threads simultaneously. 90 | 91 | @ingroup API 92 | 93 | @returns 94 | This function returns 0 on success and -1 on error. 95 | */ 96 | int HID_API_EXPORT HID_API_CALL hid_init(void); 97 | 98 | /** @brief Finalize the HIDAPI library. 99 | 100 | This function frees all of the static data associated with 101 | HIDAPI. It should be called at the end of execution to avoid 102 | memory leaks. 103 | 104 | @ingroup API 105 | 106 | @returns 107 | This function returns 0 on success and -1 on error. 108 | */ 109 | int HID_API_EXPORT HID_API_CALL hid_exit(void); 110 | 111 | /** @brief Enumerate the HID Devices. 112 | 113 | This function returns a linked list of all the HID devices 114 | attached to the system which match vendor_id and product_id. 115 | If @p vendor_id and @p product_id are both set to 0, then 116 | all HID devices will be returned. 117 | 118 | @ingroup API 119 | @param vendor_id The Vendor ID (VID) of the types of device 120 | to open. 121 | @param product_id The Product ID (PID) of the types of 122 | device to open. 123 | 124 | @returns 125 | This function returns a pointer to a linked list of type 126 | struct #hid_device, containing information about the HID devices 127 | attached to the system, or NULL in the case of failure. Free 128 | this linked list by calling hid_free_enumeration(). 129 | */ 130 | struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); 131 | 132 | /** @brief Free an enumeration Linked List 133 | 134 | This function frees a linked list created by hid_enumerate(). 135 | 136 | @ingroup API 137 | @param devs Pointer to a list of struct_device returned from 138 | hid_enumerate(). 139 | */ 140 | void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); 141 | 142 | /** @brief Open a HID device using a Vendor ID (VID), Product ID 143 | (PID) and optionally a serial number. 144 | 145 | If @p serial_number is NULL, the first device with the 146 | specified VID and PID is opened. 147 | 148 | @ingroup API 149 | @param vendor_id The Vendor ID (VID) of the device to open. 150 | @param product_id The Product ID (PID) of the device to open. 151 | @param serial_number The Serial Number of the device to open 152 | (Optionally NULL). 153 | 154 | @returns 155 | This function returns a pointer to a #hid_device object on 156 | success or NULL on failure. 157 | */ 158 | HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, wchar_t *serial_number); 159 | 160 | /** @brief Open a HID device by its path name. 161 | 162 | The path name be determined by calling hid_enumerate(), or a 163 | platform-specific path name can be used (eg: /dev/hidraw0 on 164 | Linux). 165 | 166 | @ingroup API 167 | @param path The path name of the device to open 168 | 169 | @returns 170 | This function returns a pointer to a #hid_device object on 171 | success or NULL on failure. 172 | */ 173 | HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); 174 | 175 | /** @brief Write an Output report to a HID device. 176 | 177 | The first byte of @p data[] must contain the Report ID. For 178 | devices which only support a single report, this must be set 179 | to 0x0. The remaining bytes contain the report data. Since 180 | the Report ID is mandatory, calls to hid_write() will always 181 | contain one more byte than the report contains. For example, 182 | if a hid report is 16 bytes long, 17 bytes must be passed to 183 | hid_write(), the Report ID (or 0x0, for devices with a 184 | single report), followed by the report data (16 bytes). In 185 | this example, the length passed in would be 17. 186 | 187 | hid_write() will send the data on the first OUT endpoint, if 188 | one exists. If it does not, it will send the data through 189 | the Control Endpoint (Endpoint 0). 190 | 191 | @ingroup API 192 | @param device A device handle returned from hid_open(). 193 | @param data The data to send, including the report number as 194 | the first byte. 195 | @param length The length in bytes of the data to send. 196 | 197 | @returns 198 | This function returns the actual number of bytes written and 199 | -1 on error. 200 | */ 201 | int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length); 202 | 203 | /** @brief Read an Input report from a HID device with timeout. 204 | 205 | Input reports are returned 206 | to the host through the INTERRUPT IN endpoint. The first byte will 207 | contain the Report number if the device uses numbered reports. 208 | 209 | @ingroup API 210 | @param dev A device handle returned from hid_open(). 211 | @param data A buffer to put the read data into. 212 | @param length The number of bytes to read. For devices with 213 | multiple reports, make sure to read an extra byte for 214 | the report number. 215 | @param milliseconds timeout in milliseconds or -1 for blocking wait. 216 | 217 | @returns 218 | This function returns the actual number of bytes read and 219 | -1 on error. 220 | */ 221 | int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); 222 | 223 | /** @brief Read an Input report from a HID device. 224 | 225 | Input reports are returned 226 | to the host through the INTERRUPT IN endpoint. The first byte will 227 | contain the Report number if the device uses numbered reports. 228 | 229 | @ingroup API 230 | @param device A device handle returned from hid_open(). 231 | @param data A buffer to put the read data into. 232 | @param length The number of bytes to read. For devices with 233 | multiple reports, make sure to read an extra byte for 234 | the report number. 235 | 236 | @returns 237 | This function returns the actual number of bytes read and 238 | -1 on error. 239 | */ 240 | int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length); 241 | 242 | /** @brief Set the device handle to be non-blocking. 243 | 244 | In non-blocking mode calls to hid_read() will return 245 | immediately with a value of 0 if there is no data to be 246 | read. In blocking mode, hid_read() will wait (block) until 247 | there is data to read before returning. 248 | 249 | Nonblocking can be turned on and off at any time. 250 | 251 | @ingroup API 252 | @param device A device handle returned from hid_open(). 253 | @param nonblock enable or not the nonblocking reads 254 | - 1 to enable nonblocking 255 | - 0 to disable nonblocking. 256 | 257 | @returns 258 | This function returns 0 on success and -1 on error. 259 | */ 260 | int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock); 261 | 262 | /** @brief Send a Feature report to the device. 263 | 264 | Feature reports are sent over the Control endpoint as a 265 | Set_Report transfer. The first byte of @p data[] must 266 | contain the Report ID. For devices which only support a 267 | single report, this must be set to 0x0. The remaining bytes 268 | contain the report data. Since the Report ID is mandatory, 269 | calls to hid_send_feature_report() will always contain one 270 | more byte than the report contains. For example, if a hid 271 | report is 16 bytes long, 17 bytes must be passed to 272 | hid_send_feature_report(): the Report ID (or 0x0, for 273 | devices which do not use numbered reports), followed by the 274 | report data (16 bytes). In this example, the length passed 275 | in would be 17. 276 | 277 | @ingroup API 278 | @param device A device handle returned from hid_open(). 279 | @param data The data to send, including the report number as 280 | the first byte. 281 | @param length The length in bytes of the data to send, including 282 | the report number. 283 | 284 | @returns 285 | This function returns the actual number of bytes written and 286 | -1 on error. 287 | */ 288 | int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length); 289 | 290 | /** @brief Get a feature report from a HID device. 291 | 292 | Make sure to set the first byte of @p data[] to the Report 293 | ID of the report to be read. Make sure to allow space for 294 | this extra byte in @p data[]. 295 | 296 | @ingroup API 297 | @param device A device handle returned from hid_open(). 298 | @param data A buffer to put the read data into, including 299 | the Report ID. Set the first byte of @p data[] to the 300 | Report ID of the report to be read. 301 | @param length The number of bytes to read, including an 302 | extra byte for the report ID. The buffer can be longer 303 | than the actual report. 304 | 305 | @returns 306 | This function returns the number of bytes read and 307 | -1 on error. 308 | */ 309 | int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length); 310 | 311 | /** @brief Close a HID device. 312 | 313 | @ingroup API 314 | @param device A device handle returned from hid_open(). 315 | */ 316 | void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device); 317 | 318 | /** @brief Get The Manufacturer String from a HID device. 319 | 320 | @ingroup API 321 | @param device A device handle returned from hid_open(). 322 | @param string A wide string buffer to put the data into. 323 | @param maxlen The length of the buffer in multiples of wchar_t. 324 | 325 | @returns 326 | This function returns 0 on success and -1 on error. 327 | */ 328 | int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen); 329 | 330 | /** @brief Get The Product String from a HID device. 331 | 332 | @ingroup API 333 | @param device A device handle returned from hid_open(). 334 | @param string A wide string buffer to put the data into. 335 | @param maxlen The length of the buffer in multiples of wchar_t. 336 | 337 | @returns 338 | This function returns 0 on success and -1 on error. 339 | */ 340 | int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen); 341 | 342 | /** @brief Get The Serial Number String from a HID device. 343 | 344 | @ingroup API 345 | @param device A device handle returned from hid_open(). 346 | @param string A wide string buffer to put the data into. 347 | @param maxlen The length of the buffer in multiples of wchar_t. 348 | 349 | @returns 350 | This function returns 0 on success and -1 on error. 351 | */ 352 | int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen); 353 | 354 | /** @brief Get a string from a HID device, based on its string index. 355 | 356 | @ingroup API 357 | @param device A device handle returned from hid_open(). 358 | @param string_index The index of the string to get. 359 | @param string A wide string buffer to put the data into. 360 | @param maxlen The length of the buffer in multiples of wchar_t. 361 | 362 | @returns 363 | This function returns 0 on success and -1 on error. 364 | */ 365 | int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen); 366 | 367 | /** @brief Get a string describing the last error which occurred. 368 | 369 | @ingroup API 370 | @param device A device handle returned from hid_open(). 371 | 372 | @returns 373 | This function returns a string containing the last error 374 | which occurred or NULL if none has occurred. 375 | */ 376 | HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device); 377 | 378 | #ifdef __cplusplus 379 | } 380 | #endif 381 | 382 | #endif 383 | 384 | -------------------------------------------------------------------------------- /home-easy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Reversed protocol of Home Easy wireless plugs (UNTESTED) 4 | * Using protocol from https://github.com/r10r/he853-remote 5 | * 6 | * Copyright (C) 2018 Jean-Christophe Rona 7 | * 8 | * This program is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU General Public License 10 | * as published by the Free Software Foundation; either version 2 11 | * of the License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "rf-ctrl.h" 29 | 30 | #define PROTOCOL_NAME "Home Easy" 31 | 32 | 33 | static int he_format_cmd(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command) { 34 | const int bit_count = 57; 35 | uint8_t tb_fx[16] = { 0x07, 0x0b, 0x0d, 0x0e, 0x13, 0x15, 0x16, 0x19, 0x1a, 0x1c, 0x03, 0x05, 0x06, 0x09, 0x0a, 0x0c }; 36 | uint8_t buf[4] = { 0x00, (uint8_t) ((device_code >> 8) & 0xff), (uint8_t) (device_code & 0xff), 0x00 }; 37 | int i; 38 | 39 | if (data_len * 8 < bit_count) { 40 | fprintf(stderr, "%s: data buffer too small (%lu available, %d needed)\n", PROTOCOL_NAME, (unsigned long) data_len, (bit_count + 7)/8); 41 | return -1; 42 | } 43 | 44 | switch (command) { 45 | case RF_CMD_OFF: 46 | break; 47 | 48 | case RF_CMD_ON: 49 | buf[3] |= 0x10; 50 | break; 51 | 52 | default: 53 | fprintf(stderr, "%s: Unsupported command %d\n", PROTOCOL_NAME, (int) command); 54 | return -1; 55 | } 56 | 57 | uint8_t gbuf[8] = { (uint8_t) (buf[0] >> 4), 58 | (uint8_t) (buf[0] & 15), 59 | (uint8_t) (buf[1] >> 4), 60 | (uint8_t) (buf[1] & 15), 61 | (uint8_t) (buf[2] >> 4), 62 | (uint8_t) (buf[2] & 15), 63 | (uint8_t) (buf[3] >> 4), 64 | (uint8_t) (buf[3] & 15) }; 65 | 66 | uint8_t kbuf[8]; 67 | for (i = 0; i < 8; i++) { 68 | kbuf[i] = (uint8_t) ((tb_fx[gbuf[i]] | 0x40) & 0x7f); 69 | } 70 | kbuf[0] |= 0x80; 71 | 72 | uint64_t t64 = 0; 73 | t64 = kbuf[0]; 74 | for (i = 1; i < 8; i++) 75 | { 76 | t64 = (t64 << 7) | kbuf[i]; 77 | } 78 | t64 = t64 << 7; 79 | 80 | data[0] = (uint8_t) (t64 >> 56); 81 | data[1] = (uint8_t) (t64 >> 48); 82 | data[2] = (uint8_t) (t64 >> 40); 83 | data[3] = (uint8_t) (t64 >> 32); 84 | data[4] = (uint8_t) (t64 >> 24); 85 | data[5] = (uint8_t) (t64 >> 16); 86 | data[6] = (uint8_t) (t64 >> 8); 87 | data[7] = (uint8_t) t64; 88 | 89 | return bit_count; 90 | } 91 | 92 | static struct timing_config he_timings = { 93 | .start_bit_h_time = 260, // 260 us 94 | .start_bit_l_time = 8600, // 8600 us 95 | .end_bit_h_time = 0, // 0 us 96 | .end_bit_l_time = 0, // 0 us 97 | .data_bit0_h_time = 260, // 260 us 98 | .data_bit0_l_time = 260, // 260 us 99 | .data_bit1_h_time = 260, // 260 us 100 | .data_bit1_l_time = 1300, // 1300 us 101 | .bit_fmt = RF_BIT_FMT_HL, 102 | .frame_count = 7, 103 | }; 104 | 105 | struct rf_protocol_driver he_driver = { 106 | .name = PROTOCOL_NAME, 107 | .cmd_name = "he", 108 | .timings = &he_timings, 109 | .format_cmd = &he_format_cmd, 110 | .remote_code_max = 0x00, 111 | .device_code_max = 0xFFFF, 112 | .needed_params = PARAM_DEVICE_ID | PARAM_COMMAND, 113 | }; 114 | -------------------------------------------------------------------------------- /idk.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Reversed protocol of Idk wireless chimes 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "rf-ctrl.h" 28 | 29 | #define PROTOCOL_NAME "Idk" 30 | 31 | 32 | static int idk_format_cmd(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command) { 33 | const uint16_t raw_zero = 0x5555; // 16 bits 34 | const int bit_count = 25; 35 | uint8_t raw_device_code = 0; 36 | int i; 37 | 38 | if (data_len * 8 < bit_count) { 39 | fprintf(stderr, "%s: data buffer too small (%lu available, %d needed)\n", PROTOCOL_NAME, (unsigned long) data_len, (bit_count + 7)/8); 40 | return -1; 41 | } 42 | 43 | switch (command) { 44 | case RF_CMD_ON: 45 | break; 46 | 47 | default: 48 | fprintf(stderr, "%s: Unsupported command %d\n", PROTOCOL_NAME, (int) command); 49 | return -1; 50 | } 51 | 52 | /* 53 | * Bits remains the same with a 1 added 54 | * after each bit (0 -> 01, 1 -> 11) 55 | */ 56 | for (i = 0; i < 8; i++) { 57 | if (device_code & (0x1 << i)) { 58 | raw_device_code |= 0x3 << (2 * i); 59 | } else { 60 | raw_device_code |= 0x1 << (2 * i); 61 | } 62 | } 63 | 64 | data[0] = raw_device_code & 0xFF; 65 | data[1] = (raw_zero & 0xFFFF) >> 8; 66 | data[2] = raw_zero & 0xFF; 67 | data[3] = raw_zero; 68 | 69 | return bit_count; 70 | } 71 | 72 | static struct timing_config idk_timings = { 73 | .start_bit_h_time = 0, // 0 us 74 | .start_bit_l_time = 7440, // 7440 us 75 | .end_bit_h_time = 0, // 0 us 76 | .end_bit_l_time = 0, // 0 us 77 | .data_bit0_h_time = 220, // 220 us 78 | .data_bit0_l_time = 800, // 800 us 79 | .data_bit1_h_time = 700, // 700 us 80 | .data_bit1_l_time = 320, // 320 us 81 | .bit_fmt = RF_BIT_FMT_HL, 82 | .frame_count = 20, 83 | }; 84 | 85 | struct rf_protocol_driver idk_driver = { 86 | .name = PROTOCOL_NAME, 87 | .cmd_name = "idk", 88 | .timings = &idk_timings, 89 | .format_cmd = &idk_format_cmd, 90 | .remote_code_max = 0x00, 91 | .device_code_max = 0xF, 92 | .needed_params = PARAM_DEVICE_ID | PARAM_COMMAND, 93 | }; 94 | -------------------------------------------------------------------------------- /ook-gpio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * OOK GPIO-based 433 MHz RF Transmitter driver (ook-gpio.ko kernel module needed) 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "rf-ctrl.h" 29 | 30 | #define HARDWARE_NAME "OOK GPIO" 31 | 32 | #define OOK_GPIO_TIMINGS_PATH "/sys/devices/platform/ook-gpio.0/timings" 33 | #define OOK_GPIO_FRAME_PATH "/sys/devices/platform/ook-gpio.0/frame" 34 | 35 | 36 | static int ook_gpio_probe(void) { 37 | if (access(OOK_GPIO_TIMINGS_PATH, W_OK ) < 0) { 38 | dbg_printf(3, "%s: no access to %s\n", HARDWARE_NAME, OOK_GPIO_TIMINGS_PATH); 39 | dbg_printf(2, "%s not detected\n", HARDWARE_NAME); 40 | return -1; 41 | } 42 | 43 | if (access(OOK_GPIO_FRAME_PATH, W_OK ) < 0) { 44 | dbg_printf(3, "%s: no access to %s\n", HARDWARE_NAME, OOK_GPIO_FRAME_PATH); 45 | dbg_printf(2, "%s not detected\n", HARDWARE_NAME); 46 | return -1; 47 | } 48 | 49 | dbg_printf(1, "%s detected\n", HARDWARE_NAME); 50 | 51 | return 0; 52 | } 53 | 54 | static int ook_gpio_init(struct rf_hardware_params *params) { 55 | return 0; 56 | } 57 | 58 | static void ook_gpio_close(void) { 59 | } 60 | 61 | static int ook_gpio_set_timings(struct timing_config *conf) { 62 | FILE *f_timings; 63 | int ret; 64 | 65 | f_timings = fopen(OOK_GPIO_TIMINGS_PATH, "w"); 66 | if (!f_timings) { 67 | fprintf(stderr, "%s: Cannot open timings path \"%s\"\n", HARDWARE_NAME, OOK_GPIO_TIMINGS_PATH); 68 | return -1; 69 | } 70 | 71 | switch (conf->bit_fmt) { 72 | case RF_BIT_FMT_HL: 73 | case RF_BIT_FMT_LH: 74 | ret = fprintf(f_timings, "%u,%u,%u,%u,%u,%u,%u,%u,%u,%u", 75 | conf->start_bit_h_time, conf->start_bit_l_time, 76 | conf->end_bit_h_time, conf->end_bit_l_time, 77 | conf->data_bit0_h_time, conf->data_bit0_l_time, 78 | conf->data_bit1_h_time, conf->data_bit1_l_time, 79 | (conf->bit_fmt == RF_BIT_FMT_HL) ? 0 : 1, conf->frame_count); 80 | break; 81 | 82 | case RF_BIT_FMT_RAW: 83 | ret = fprintf(f_timings, "%u,0,0,0,0,0,0,0,2,%u", 84 | conf->base_time, conf->frame_count); 85 | break; 86 | 87 | default: 88 | fprintf(stderr, "%s: Bit format %s is not supported !\n", HARDWARE_NAME, rf_bit_fmt_str[(int) conf->bit_fmt]); 89 | fclose(f_timings); 90 | return -1; 91 | } 92 | 93 | if (ret < 0) { 94 | fprintf(stderr, "%s: Cannot write timings\n", HARDWARE_NAME); 95 | } 96 | 97 | fclose(f_timings); 98 | 99 | return (ret < 0) ? ret : 0; 100 | } 101 | 102 | static int ook_gpio_send_frame(uint8_t *frame_data, uint16_t bit_count) { 103 | FILE *f_frame; 104 | uint32_t str_size; 105 | char *str; 106 | unsigned int byte_count = (bit_count + 7)/8; 107 | int ret; 108 | int i; 109 | 110 | f_frame = fopen(OOK_GPIO_FRAME_PATH, "w"); 111 | if (!f_frame) { 112 | fprintf(stderr, "%s: Cannot open frame path \"%s\"\n", HARDWARE_NAME, OOK_GPIO_FRAME_PATH); 113 | return -1; 114 | } 115 | 116 | /* Size of the string: 1 byte + n bytes + n ',' + '\0' = 4 * (n + 1) */ 117 | str_size = 4 * (byte_count + 1); 118 | str = (char*) malloc(str_size * sizeof(char)); 119 | 120 | sprintf(str, "%u", bit_count); 121 | for (i = 0; i < byte_count; i++) { 122 | sprintf(str, "%s,%u", str, frame_data[i]); 123 | } 124 | 125 | ret = fprintf(f_frame, "%s", str); 126 | if (ret < 0) { 127 | fprintf(stderr, "%s: Cannot write frame\n", HARDWARE_NAME); 128 | } 129 | 130 | free(str); 131 | fclose(f_frame); 132 | 133 | return (ret < 0) ? ret : 0; 134 | } 135 | 136 | static int ook_gpio_send_cmd(struct timing_config *config, uint8_t *frame_data, uint16_t bit_count) { 137 | int ret = 0; 138 | 139 | ret = ook_gpio_set_timings(config); 140 | if (ret < 0) { 141 | fprintf(stderr, "%s timings configuration failed\n", HARDWARE_NAME); 142 | return ret; 143 | } 144 | 145 | return ook_gpio_send_frame(frame_data, bit_count); 146 | } 147 | 148 | struct rf_hardware_driver ook_gpio_driver = { 149 | .name = HARDWARE_NAME, 150 | .cmd_name = "ook-gpio", 151 | .long_name = "OOK GPIO-based 433 MHz RF Transmitter", 152 | .supported_bit_fmts = (1 << RF_BIT_FMT_HL) | (1 << RF_BIT_FMT_LH) | (1 << RF_BIT_FMT_RAW), 153 | .probe = &ook_gpio_probe, 154 | .init = &ook_gpio_init, 155 | .close = &ook_gpio_close, 156 | .send_cmd = &ook_gpio_send_cmd, 157 | }; 158 | 159 | -------------------------------------------------------------------------------- /otax.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Reversed protocol of OTAX wireless plugs 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "rf-ctrl.h" 28 | 29 | #define PROTOCOL_NAME "OTAX" 30 | 31 | 32 | static int otax_format_cmd(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command) { 33 | const int bit_count = 25; 34 | uint8_t raw_cmd; // 4 bits 35 | uint16_t raw_device_code = 0; // 10 bits 36 | uint16_t raw_remote_code = 0; // 10 bits 37 | int i; 38 | 39 | if (data_len * 8 < bit_count) { 40 | fprintf(stderr, "%s: data buffer too small (%lu available, %d needed)\n", PROTOCOL_NAME, (unsigned long) data_len, (bit_count + 7)/8); 41 | return -1; 42 | } 43 | 44 | switch (command) { 45 | case RF_CMD_OFF: 46 | raw_cmd = 0x01; 47 | break; 48 | 49 | case RF_CMD_ON: 50 | raw_cmd = 0x04; 51 | break; 52 | 53 | default: 54 | fprintf(stderr, "%s: Unsupported command %d\n", PROTOCOL_NAME, (int) command); 55 | return -1; 56 | } 57 | 58 | /* 59 | * Bits of the remote_code remains the same with 60 | * a 1 added after each bit (0 -> 01, 1 -> 11) 61 | */ 62 | for (i = 0; i < 5; i++) { 63 | if (remote_code & (0x1 << i)) { 64 | raw_remote_code |= 0x3 << (2 * i); 65 | } else { 66 | raw_remote_code |= 0x1 << (2 * i); 67 | } 68 | } 69 | 70 | /* 71 | * Bits of the device_code are inverted with a 72 | * 0 added before each bit (0 -> 01, 1 -> 00) 73 | */ 74 | for (i = 0; i < 5; i++) { 75 | if (!(device_code & (0x1 << i))) { 76 | raw_device_code |= 0x1 << (2 * i); 77 | } 78 | } 79 | 80 | data[0] = (raw_remote_code & 0x3FF) >> 2; 81 | data[1] = ((raw_remote_code & 0x3) << 6) | ((raw_device_code & 0x3FF) >> 4); 82 | data[2] = ((raw_device_code & 0xF) << 4) | (raw_cmd & 0xF); 83 | data[3] = 0; 84 | 85 | return bit_count; 86 | } 87 | 88 | static struct timing_config otax_timings = { 89 | .start_bit_h_time = 0, // 0 us 90 | .start_bit_l_time = 4860, // 4860 us 91 | .end_bit_h_time = 0, // 0 us 92 | .end_bit_l_time = 0, // 0 us 93 | .data_bit0_h_time = 160, // 160 us 94 | .data_bit0_l_time = 420, // 420 us 95 | .data_bit1_h_time = 420, // 420 us 96 | .data_bit1_l_time = 160, // 160 us 97 | .bit_fmt = RF_BIT_FMT_HL, 98 | .frame_count = 10, 99 | }; 100 | 101 | struct rf_protocol_driver otax_driver = { 102 | .name = PROTOCOL_NAME, 103 | .cmd_name = "otax", 104 | .timings = &otax_timings, 105 | .format_cmd = &otax_format_cmd, 106 | .remote_code_max = 0x1F, 107 | .device_code_max = 0x1F, 108 | .needed_params = PARAM_REMOTE_ID | PARAM_DEVICE_ID | PARAM_COMMAND, 109 | }; 110 | -------------------------------------------------------------------------------- /raw.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Helper functions for RAW frame generation 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "rf-ctrl.h" 29 | #include "raw.h" 30 | 31 | 32 | size_t raw_write_low(uint8_t *buf, size_t offset, uint8_t length) { 33 | size_t i; 34 | 35 | for (i = offset; i < (offset + length); i++) { 36 | buf[i/8] &= ~(1 << (7 - (i % 8))); 37 | } 38 | 39 | return length; 40 | } 41 | 42 | size_t raw_write_high(uint8_t *buf, size_t offset, uint8_t length) { 43 | size_t i; 44 | 45 | for (i = offset; i < (offset + length); i++) { 46 | buf[i/8] |= 1 << (7 - (i % 8)); 47 | } 48 | 49 | return length; 50 | } 51 | 52 | size_t raw_write_edge(uint8_t *buf, size_t offset, raw_edge_order_t order, uint8_t h_len, uint8_t l_len) { 53 | size_t count = offset; 54 | 55 | if (order == RAW_EDGE_ORDER_HL) { 56 | /* High, then low */ 57 | count += raw_write_high(buf, count, h_len); 58 | count += raw_write_low(buf, count, l_len); 59 | } else { 60 | /* Low, then high */ 61 | count += raw_write_low(buf, count, l_len); 62 | count += raw_write_high(buf, count, h_len); 63 | } 64 | 65 | return (count - offset); 66 | } 67 | 68 | size_t raw_write_bits(uint8_t *buf, size_t offset, uint8_t *data, size_t data_bit_len, raw_edge_order_t zero_order, uint8_t zero_h_len, uint8_t zero_l_len, raw_edge_order_t one_order, uint8_t one_h_len, uint8_t one_l_len) { 69 | size_t count = offset; 70 | size_t i; 71 | 72 | for (i = 0; i < data_bit_len; i++) { 73 | if ((data[i/8] & (1 << (7 - (i % 8)))) != 0) { 74 | count += raw_write_edge(buf, count, one_order, one_h_len, one_l_len); 75 | } else { 76 | count += raw_write_edge(buf, count, zero_order, zero_h_len, zero_l_len); 77 | } 78 | } 79 | 80 | return (count - offset); 81 | } 82 | 83 | int raw_generate_hl_frame(uint8_t *dest_frame_data, size_t dest_data_len, struct timing_config *config, uint8_t *src_frame_data, uint16_t src_bit_count, uint16_t base_time) { 84 | uint16_t i; 85 | size_t count = 0; 86 | raw_edge_order_t order = (config->bit_fmt == RF_BIT_FMT_HL) ? RAW_EDGE_ORDER_HL : RAW_EDGE_ORDER_LH; 87 | uint8_t start_h_len = round(config->start_bit_h_time/base_time); 88 | uint8_t start_l_len = round(config->start_bit_l_time/base_time); 89 | uint8_t end_h_len = round(config->end_bit_h_time/base_time); 90 | uint8_t end_l_len = round(config->end_bit_l_time/base_time); 91 | uint8_t zero_h_len = round(config->data_bit0_h_time/base_time); 92 | uint8_t zero_l_len = round(config->data_bit0_l_time/base_time); 93 | uint8_t one_h_len = round(config->data_bit1_h_time/base_time); 94 | uint8_t one_l_len = round(config->data_bit1_l_time/base_time); 95 | 96 | /* Compute the final length of the frame */ 97 | count = 0; 98 | for (i = 0; i < src_bit_count; i++) { 99 | if (src_frame_data[i/8] & (1 << (i % 8))) { 100 | count++; 101 | } 102 | } 103 | 104 | count = start_h_len + start_l_len + end_h_len + end_l_len + 105 | (zero_h_len + zero_l_len) * (src_bit_count - count) + 106 | (one_h_len + one_l_len) * count; 107 | 108 | if (count > (dest_data_len * 8)) { 109 | fprintf(stderr, "RAW buffer to small (%lu bits) for this frame (%lu bits)\n", dest_data_len * 8, count); 110 | return -1; 111 | } 112 | 113 | /* Generate the RAW frame */ 114 | count = 0; 115 | count += raw_write_edge(dest_frame_data, count, order, start_h_len, start_l_len); 116 | count += raw_write_bits(dest_frame_data, count, src_frame_data, src_bit_count, order, zero_h_len, zero_l_len, order, one_h_len, one_l_len); 117 | count += raw_write_edge(dest_frame_data, count, order, end_h_len, end_l_len); 118 | 119 | return count; 120 | } 121 | -------------------------------------------------------------------------------- /raw.h: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Helper functions for RAW frame generation 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #ifndef _RAW_H_ 23 | #define _RAW_H_ 24 | 25 | typedef enum { 26 | RAW_EDGE_ORDER_HL = 0, 27 | RAW_EDGE_ORDER_LH = 1, 28 | } raw_edge_order_t; 29 | 30 | size_t raw_write_low(uint8_t *buf, size_t offset, uint8_t length); 31 | size_t raw_write_high(uint8_t *buf, size_t offset, uint8_t length); 32 | size_t raw_write_edge(uint8_t *buf, size_t offset, raw_edge_order_t order, uint8_t h_len, uint8_t l_len); 33 | size_t raw_write_bits(uint8_t *buf, size_t offset, uint8_t *data, size_t data_bit_len, raw_edge_order_t zero_order, uint8_t zero_h_len, uint8_t zero_l_len, raw_edge_order_t one_order, uint8_t one_h_len, uint8_t one_l_len); 34 | int raw_generate_hl_frame(uint8_t *dest_frame_data, size_t dest_data_len, struct timing_config *config, uint8_t *src_frame_data, uint16_t src_bit_count, uint16_t base_time); 35 | 36 | #endif /* _RAW_H_ */ 37 | -------------------------------------------------------------------------------- /rf-ctrl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * 4 | * Copyright (C) 2018 Jean-Christophe Rona 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "rf-ctrl.h" 34 | #include "raw.h" 35 | 36 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 37 | 38 | #define APP_NAME "rf-ctrl" 39 | #define APP_VERSION "0.6" 40 | #define COPYRIGHT_DATE "2018" 41 | #define AUTHOR_NAME "Jean-Christophe Rona" 42 | 43 | #define STORAGE_PATH_BASE "."APP_NAME 44 | 45 | #define MAX_FRAME_LENGTH 512 46 | 47 | #define DEFAULT_RAW_FALLBACK_ACCURACY 90 // This changes how accurate will be the base_time for generated RAW frames (100 minus the allowed error in % of the shortest timing) 48 | 49 | #ifndef CONFIG_FILE_LOCATION 50 | #define CONFIG_FILE_LOCATION "/etc" 51 | #endif 52 | 53 | #define CONFIG_FILE_PATH CONFIG_FILE_LOCATION"/"APP_NAME".conf" 54 | #define CONFIG_LINE_MAX 1024 // bytes 55 | 56 | #define CONFIG_FIELD_HARDWARE "HARDWARE" 57 | #define CONFIG_FIELD_GPIO "GPIO" 58 | #define CONFIG_FIELD_RAW "FORCE_RAW" 59 | #define CONFIG_FIELD_ACCURACY "RAW_ACCURACY" 60 | 61 | #define CONFIG_VALUE_TRUE "TRUE" 62 | #define CONFIG_VALUE_FALSE "FALSE" 63 | 64 | /* WARNING: Needs to remain in-sync with PARAM_* defines in rf-ctrl.h */ 65 | char *(parameter_str[]) = { 66 | "hw", 67 | "proto", 68 | "remote", 69 | "device", 70 | "command", 71 | "scan", 72 | "nframe", 73 | "accuracy", 74 | "raw", 75 | "gpio", 76 | "verbose", 77 | }; 78 | 79 | /* WARNING: Needs to remain in-sync with rf_command_t enum in rf-ctrl.h */ 80 | char *(rf_command_str[]) = { 81 | "OFF", 82 | "ON", 83 | "Group OFF", 84 | "Group ON", 85 | "Prog", 86 | "F1", 87 | "F2", 88 | "F3", 89 | }; 90 | 91 | char *(rf_cmdline_command_str[]) = { 92 | "off", 93 | "on", 94 | "goff", 95 | "gon", 96 | "prog", 97 | "f1", 98 | "f2", 99 | "f3", 100 | }; 101 | 102 | /* WARNING: Needs to remain in-sync with rf_bit_fmt_t enum in rf-ctrl.h */ 103 | char *(rf_bit_fmt_str[]) = { 104 | "H-L", 105 | "L-H", 106 | "Raw", 107 | }; 108 | 109 | static struct rf_hardware_driver *current_hw_driver = NULL; 110 | 111 | static int debug_level = 0; 112 | 113 | static uint8_t raw_fallback_accuracy = DEFAULT_RAW_FALLBACK_ACCURACY; 114 | 115 | extern struct rf_protocol_driver otax_driver; 116 | extern struct rf_protocol_driver dio_driver; 117 | extern struct rf_protocol_driver he_driver; 118 | extern struct rf_protocol_driver idk_driver; 119 | extern struct rf_protocol_driver sumtech_driver; 120 | extern struct rf_protocol_driver auchan_driver; 121 | extern struct rf_protocol_driver auchan2_driver; 122 | extern struct rf_protocol_driver somfy_driver; 123 | extern struct rf_protocol_driver blyss_driver; 124 | 125 | extern struct rf_hardware_driver he853_driver; 126 | extern struct rf_hardware_driver ook_gpio_driver; 127 | extern struct rf_hardware_driver sysfs_gpio_driver; 128 | #ifdef ALSA_ENABLED 129 | extern struct rf_hardware_driver alsa_driver; 130 | #endif 131 | extern struct rf_hardware_driver dummy_driver; 132 | 133 | struct rf_protocol_driver *(protocol_drivers[]) = { 134 | &otax_driver, 135 | &dio_driver, 136 | &he_driver, 137 | &idk_driver, 138 | &sumtech_driver, 139 | &auchan_driver, 140 | &auchan2_driver, 141 | &somfy_driver, 142 | &blyss_driver, 143 | }; 144 | 145 | struct rf_hardware_driver *(hardware_drivers[]) = { 146 | &he853_driver, 147 | &ook_gpio_driver, 148 | &sysfs_gpio_driver, 149 | #ifdef ALSA_ENABLED 150 | &alsa_driver, 151 | #endif 152 | &dummy_driver, 153 | }; 154 | 155 | int is_dbg_enabled(int level) { 156 | return (level <= debug_level); 157 | } 158 | 159 | void dbg_printf(int level, char *buff, ...) { 160 | va_list arglist; 161 | 162 | if (level > debug_level) { 163 | return; 164 | } 165 | 166 | va_start(arglist, buff); 167 | 168 | vfprintf(stdout, buff, arglist); 169 | 170 | va_end(arglist); 171 | } 172 | 173 | static void mkdir_p(const char *dir) { 174 | char *tmp, *p = NULL; 175 | size_t len; 176 | 177 | tmp = strdup(dir); 178 | len = strlen(tmp); 179 | 180 | if (tmp[len - 1] == '/') { 181 | tmp[len - 1] = 0; 182 | } 183 | 184 | for (p = tmp + 1; *p != '\0'; p++) { 185 | if (*p == '/') { 186 | *p = 0; 187 | mkdir(tmp, S_IRWXU); 188 | *p = '/'; 189 | } 190 | } 191 | mkdir(tmp, S_IRWXU); 192 | 193 | free(tmp); 194 | } 195 | 196 | void get_storage_path(char *path, struct rf_protocol_driver *protocol) { 197 | struct stat st = {0}; 198 | struct passwd *pw = getpwuid(getuid()); 199 | 200 | snprintf(path, STORAGE_PATH_MAX_LEN, "%s/%s/%s", pw->pw_dir, STORAGE_PATH_BASE, protocol->cmd_name); 201 | 202 | /* Create the folder if it does not exist */ 203 | if (stat(path, &st) < 0) { 204 | mkdir_p(path); 205 | } 206 | } 207 | 208 | static struct rf_protocol_driver * get_protocol_driver_by_id(unsigned int protocol) { 209 | if (protocol >= ARRAY_SIZE(protocol_drivers)) { 210 | return NULL; 211 | } 212 | 213 | return protocol_drivers[protocol]; 214 | } 215 | 216 | static struct rf_protocol_driver * get_protocol_driver_by_name(char *protocol_str) { 217 | int i; 218 | 219 | for (i = 0; i < ARRAY_SIZE(protocol_drivers); i++) { 220 | if (!strcmp(protocol_drivers[i]->cmd_name, protocol_str)) { 221 | return protocol_drivers[i]; 222 | } 223 | } 224 | 225 | return NULL; 226 | } 227 | 228 | static int get_protocol_id_by_name(char *protocol_str) { 229 | int i; 230 | 231 | for (i = 0; i < ARRAY_SIZE(protocol_drivers); i++) { 232 | if (!strcmp(protocol_drivers[i]->cmd_name, protocol_str)) { 233 | return i; 234 | } 235 | } 236 | 237 | return -1; 238 | } 239 | 240 | static struct rf_hardware_driver * get_hw_driver_by_id(unsigned int hw) { 241 | if (hw >= ARRAY_SIZE(hardware_drivers)) { 242 | return NULL; 243 | } 244 | 245 | return hardware_drivers[hw]; 246 | } 247 | 248 | static struct rf_hardware_driver * get_hw_driver_by_name(char *hw_str) { 249 | int i; 250 | 251 | for (i = 0; i < ARRAY_SIZE(hardware_drivers); i++) { 252 | if (!strcmp(hardware_drivers[i]->cmd_name, hw_str)) { 253 | return hardware_drivers[i]; 254 | } 255 | } 256 | 257 | return NULL; 258 | } 259 | 260 | static struct rf_hardware_driver * auto_detect_hw_driver(void) { 261 | int i; 262 | 263 | for (i = 0; i < ARRAY_SIZE(hardware_drivers); i++) { 264 | if (!hardware_drivers[i]->probe()) { 265 | return hardware_drivers[i]; 266 | } 267 | } 268 | 269 | return NULL; 270 | } 271 | 272 | static int get_hw_id_by_name(char *hw_str) { 273 | int i; 274 | 275 | for (i = 0; i < ARRAY_SIZE(hardware_drivers); i++) { 276 | if (!strcmp(hardware_drivers[i]->cmd_name, hw_str)) { 277 | return i; 278 | } 279 | } 280 | 281 | return -1; 282 | } 283 | 284 | static int get_cmd_id_by_name(char *cmd_str) { 285 | int i; 286 | 287 | for (i = 0; i < ARRAY_SIZE(rf_cmdline_command_str); i++) { 288 | if (!strcmp(rf_cmdline_command_str[i], cmd_str)) { 289 | return i; 290 | } 291 | } 292 | 293 | return -1; 294 | } 295 | 296 | static int parse_config_file(uint16_t *provided_params, int *hw, struct rf_hardware_params *hw_params) { 297 | FILE * f; 298 | char line[CONFIG_LINE_MAX]; 299 | char *field, *value; 300 | char *p; 301 | 302 | f = fopen(CONFIG_FILE_PATH, "r"); 303 | if (f == NULL) { 304 | dbg_printf(1, "Warning: Cannot open %s\n", CONFIG_FILE_PATH); 305 | return -1; 306 | } 307 | 308 | while (fgets(line, sizeof(line), f) != NULL) { 309 | /* Remove leading spaces */ 310 | field = line; 311 | while (*field == ' ' || *field == '\t') { 312 | field++; 313 | } 314 | 315 | /* Detect comments */ 316 | if (*field == '#') { 317 | continue; 318 | } 319 | 320 | value = strstr(line, "="); 321 | if (value == NULL) { 322 | continue; 323 | } 324 | 325 | /* Remove '=' */ 326 | p = value; 327 | *value++ = '\0'; 328 | 329 | /* Remove trailing spaces */ 330 | while (p > field && (*p == ' ' || *p == '\t' || *p == '\0')) { 331 | *p-- = '\0'; 332 | } 333 | 334 | /* Remove leading spaces */ 335 | while (*value == ' ' || *value == '\t') { 336 | value++; 337 | } 338 | 339 | /* Remove trailing spaces */ 340 | p = value + strlen(value); 341 | while (p > value && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\0')) { 342 | *p-- = '\0'; 343 | } 344 | 345 | if (!strncmp(field, CONFIG_FIELD_HARDWARE, sizeof(CONFIG_FIELD_HARDWARE) - 1)) { 346 | *hw = strtoul(value, &p, 0); 347 | if (*p != '\0') { 348 | *hw = get_hw_id_by_name(value); 349 | } 350 | 351 | if (*hw < 0 || *hw >= ARRAY_SIZE(hardware_drivers)) { 352 | fprintf(stderr, "Unsupported RF hardware %s in configuration file\n", value); 353 | continue; 354 | } 355 | 356 | *provided_params |= PARAM_HARDWARE; 357 | } else if (!strncmp(field, CONFIG_FIELD_GPIO, sizeof(CONFIG_FIELD_GPIO) - 1)) { 358 | hw_params->gpio = strtoul(value, &p, 0); 359 | *provided_params |= PARAM_GPIO; 360 | } else if (!strncmp(field, CONFIG_FIELD_RAW, sizeof(CONFIG_FIELD_RAW) - 1)) { 361 | if (!strncmp(value, CONFIG_VALUE_TRUE, sizeof(CONFIG_VALUE_TRUE) - 1)) { 362 | *provided_params |= PARAM_RAW; 363 | } 364 | } else if (!strncmp(field, CONFIG_FIELD_ACCURACY, sizeof(CONFIG_FIELD_ACCURACY) - 1)) { 365 | raw_fallback_accuracy = strtoul(value, NULL, 0); 366 | if (raw_fallback_accuracy > 100) { 367 | fprintf(stderr, "Invalid accuracy %s in configuration file\n", value); 368 | continue; 369 | } 370 | 371 | *provided_params |= PARAM_ACCURACY; 372 | } 373 | } 374 | 375 | fclose(f); 376 | } 377 | 378 | static int gcd(uint16_t a, uint16_t b, uint16_t accuracy) { 379 | if (a <= accuracy) { 380 | return b; 381 | } 382 | 383 | return gcd(b % a, a, accuracy); 384 | } 385 | 386 | static uint16_t find_best_base_time(struct timing_config *config) { 387 | uint16_t gcd1, gcd2, gcd3, gcd4; 388 | uint16_t accuracy = 0xFFFF; 389 | 390 | if ((config->start_bit_h_time > 0) && (accuracy > config->start_bit_h_time)) { 391 | accuracy = config->start_bit_h_time; 392 | } 393 | 394 | if ((config->start_bit_l_time > 0) && (accuracy > config->start_bit_l_time)) { 395 | accuracy = config->start_bit_l_time; 396 | } 397 | 398 | if ((config->end_bit_h_time > 0) && (accuracy > config->end_bit_h_time)) { 399 | accuracy = config->end_bit_h_time; 400 | } 401 | 402 | if ((config->end_bit_l_time > 0) && (accuracy > config->end_bit_l_time)) { 403 | accuracy = config->end_bit_l_time; 404 | } 405 | 406 | if ((config->data_bit0_h_time > 0) && (accuracy > config->data_bit0_h_time)) { 407 | accuracy = config->data_bit0_h_time; 408 | } 409 | 410 | if ((config->data_bit0_l_time > 0) && (accuracy > config->data_bit0_l_time)) { 411 | accuracy = config->data_bit0_l_time; 412 | } 413 | 414 | if ((config->data_bit1_h_time > 0) && (accuracy > config->data_bit1_h_time)) { 415 | accuracy = config->data_bit1_h_time; 416 | } 417 | 418 | if ((config->data_bit1_l_time > 0) && (accuracy > config->data_bit1_l_time)) { 419 | accuracy = config->data_bit1_l_time; 420 | } 421 | 422 | accuracy = (accuracy * (100 - raw_fallback_accuracy))/100; 423 | 424 | dbg_printf(3, " RAW Accuracy: %u us (%u%%)\n", accuracy, raw_fallback_accuracy); 425 | 426 | gcd1 = gcd(config->start_bit_h_time, config->start_bit_l_time, accuracy); 427 | gcd2 = gcd(config->end_bit_h_time, config->end_bit_l_time, accuracy); 428 | gcd3 = gcd(config->data_bit0_h_time, config->data_bit0_l_time, accuracy); 429 | gcd4 = gcd(config->data_bit1_h_time, config->data_bit1_l_time, accuracy); 430 | 431 | gcd1 = gcd(gcd1, gcd2, accuracy); 432 | gcd3 = gcd(gcd3, gcd4, accuracy); 433 | 434 | return (uint16_t) gcd(gcd1, gcd3, accuracy); 435 | } 436 | 437 | static int send_cmd(uint32_t remote_code, uint32_t device_code, rf_command_t command, int protocol, uint8_t force_raw) { 438 | struct rf_protocol_driver *protocol_driver; 439 | uint8_t data[MAX_FRAME_LENGTH]; 440 | int data_bit_count; 441 | struct timing_config fallback_timings; 442 | uint8_t fallback_data[MAX_FRAME_LENGTH]; 443 | int fallback_data_bit_count; 444 | uint16_t base_time; 445 | int ret = 0; 446 | int i; 447 | 448 | protocol_driver = get_protocol_driver_by_id(protocol); 449 | 450 | data_bit_count = protocol_driver->format_cmd(data, sizeof(data), remote_code, device_code, command); 451 | if (data_bit_count < 0) { 452 | fprintf(stderr, "%s - %s: Format command failed\n", current_hw_driver->name, protocol_driver->name); 453 | return data_bit_count; 454 | } 455 | 456 | printf("Sending %s command '%s'", protocol_drivers[protocol]->name, rf_command_str[(int) command]); 457 | 458 | if (protocol_drivers[protocol]->needed_params & PARAM_DEVICE_ID) { 459 | printf(" to device ID %u", device_code); 460 | } 461 | 462 | if (protocol_drivers[protocol]->needed_params & PARAM_REMOTE_ID) { 463 | printf(", using remote ID %u", remote_code); 464 | } 465 | 466 | printf(" ...\n"); 467 | 468 | if (is_dbg_enabled(3)) { 469 | dbg_printf(3, " Timings (%s): Bit Format %s - Frame Count %u\n", protocol_driver->name, 470 | rf_bit_fmt_str[(int) protocol_driver->timings->bit_fmt], protocol_driver->timings->frame_count); 471 | 472 | switch (protocol_driver->timings->bit_fmt) { 473 | case RF_BIT_FMT_HL: 474 | case RF_BIT_FMT_LH: 475 | dbg_printf(3, " Timings (%s): Start-bit HTime %u us - Start-bit LTime %u us\n", protocol_driver->name, 476 | protocol_driver->timings->start_bit_h_time, protocol_driver->timings->start_bit_l_time); 477 | dbg_printf(3, " Timings (%s): End-bit HTime %u us - End-bit LTime %u us\n", protocol_driver->name, 478 | protocol_driver->timings->end_bit_h_time, protocol_driver->timings->end_bit_l_time); 479 | dbg_printf(3, " Timings (%s): Data-bit0 HTime %u us - Data-bit0 LTime %u us\n", protocol_driver->name, 480 | protocol_driver->timings->data_bit0_h_time, protocol_driver->timings->data_bit0_l_time); 481 | dbg_printf(3, " Timings (%s): Data-bit1 HTime %u us - Data-bit1 LTime %u us\n", protocol_driver->name, 482 | protocol_driver->timings->data_bit1_h_time, protocol_driver->timings->data_bit1_l_time); 483 | break; 484 | 485 | case RF_BIT_FMT_RAW: 486 | dbg_printf(3, " Timings (%s): Base HLTime %u us\n", protocol_driver->name, 487 | protocol_driver->timings->base_time); 488 | break; 489 | } 490 | } 491 | 492 | if (is_dbg_enabled(1)) { 493 | dbg_printf(1, " Frame data (%s):", protocol_driver->name); 494 | for (i = 0; i < (data_bit_count + 7)/8; i++) { 495 | dbg_printf(1, " %02X", data[i]); 496 | } 497 | dbg_printf(1, "\n"); 498 | } 499 | 500 | if (protocol_driver->timings->bit_fmt != RF_BIT_FMT_RAW && (!(current_hw_driver->supported_bit_fmts & (1 << protocol_driver->timings->bit_fmt)) || force_raw)) { 501 | if (current_hw_driver->supported_bit_fmts & (1 << RF_BIT_FMT_RAW)) { 502 | dbg_printf(1, "\n"); 503 | if (!force_raw) { 504 | dbg_printf(1, " Requested bit format not supported by %s, falling back to RAW\n", current_hw_driver->name); 505 | } 506 | 507 | /* Generate a RAW frame */ 508 | base_time = find_best_base_time(protocol_driver->timings); 509 | 510 | fallback_data_bit_count = raw_generate_hl_frame(fallback_data, sizeof(fallback_data), protocol_driver->timings, data, (uint16_t) data_bit_count, base_time); 511 | 512 | fallback_timings.base_time = base_time; 513 | fallback_timings.bit_fmt = RF_BIT_FMT_RAW; 514 | fallback_timings.frame_count = protocol_driver->timings->frame_count; 515 | 516 | if (is_dbg_enabled(1)) { 517 | dbg_printf(3, " RAW Timings (%s): Base HLTime %u us\n", protocol_driver->name, 518 | fallback_timings.base_time); 519 | 520 | dbg_printf(1, " RAW Frame data (%s):", protocol_driver->name); 521 | for (i = 0; i < (fallback_data_bit_count + 7)/8; i++) { 522 | dbg_printf(1, " %02X", fallback_data[i]); 523 | } 524 | dbg_printf(1, "\n"); 525 | } 526 | 527 | ret = current_hw_driver->send_cmd(&fallback_timings, fallback_data, (uint16_t) fallback_data_bit_count); 528 | } else { 529 | fprintf(stderr, "%s - %s: Bit format %u not supported\n", current_hw_driver->name, protocol_driver->name, protocol_driver->timings->bit_fmt); 530 | return -1; 531 | } 532 | } else { 533 | ret = current_hw_driver->send_cmd(protocol_driver->timings, data, (uint16_t) data_bit_count); 534 | } 535 | 536 | if (ret < 0) { 537 | fprintf(stderr, "%s - %s: configuration failed\n", current_hw_driver->name, protocol_driver->name); 538 | return ret; 539 | } 540 | 541 | return 0; 542 | } 543 | 544 | static void usage(FILE * fp, int argc, char **argv) { 545 | int i; 546 | 547 | fprintf(fp,"\n" 548 | APP_NAME" v"APP_VERSION" - (C)"COPYRIGHT_DATE" "AUTHOR_NAME"\n\n" 549 | "Usage: %s [options]\n\n" 550 | "Options:\n" 551 | " -H | --hw Hardware driver to use (otherwise try to auto-detect)\n" 552 | " -p | --proto Protocol to use\n" 553 | " -r | --remote Remote ID to take\n" 554 | " -d | --device Device ID to reach\n" 555 | " -c | --command Command to send\n" 556 | " -s | --scan Perform a brute force scan (-p, -r and -d can be used to force specific values)\n" 557 | " -n | --nframe <0-255> Number of frames to send (override per protocol default value)\n" 558 | " -a | --accuracy <0-100> Accuracy of the timings in percent when HL frames are converted to RAW (default %u%%)\n" 559 | " -R | --raw Convert HL frames to RAW if possible\n" 560 | " -g | --gpio Which GPIO to use to transmit the signal (only for hardware drivers supporting it)\n" 561 | " -v | --verbose Print more detailed information (-vv and -vvv for even more details)\n" 562 | " -h | --help Print this message\n\n", 563 | argv[0], DEFAULT_RAW_FALLBACK_ACCURACY); 564 | 565 | fprintf(fp, "Available hardware drivers:"); 566 | for (i = 0; i < ARRAY_SIZE(hardware_drivers); i++) { 567 | if (i > 0) { 568 | fprintf(fp, ","); 569 | } 570 | fprintf(fp, " %s (%s)", hardware_drivers[i]->cmd_name, hardware_drivers[i]->name); 571 | } 572 | fprintf(fp, "\n"); 573 | 574 | fprintf(fp, "Available protocols:"); 575 | for (i = 0; i < ARRAY_SIZE(protocol_drivers); i++) { 576 | if (i > 0) { 577 | fprintf(fp, ","); 578 | } 579 | fprintf(fp, " %s (%s)", protocol_drivers[i]->cmd_name, protocol_drivers[i]->name); 580 | } 581 | fprintf(fp, "\n"); 582 | 583 | fprintf(fp, "Available commands:"); 584 | for (i = 0; i < ARRAY_SIZE(rf_command_str); i++) { 585 | if (i > 0) { 586 | fprintf(fp, ","); 587 | } 588 | fprintf(fp, " %s (%s)", rf_cmdline_command_str[i], rf_command_str[i]); 589 | } 590 | fprintf(fp, "\n\n"); 591 | 592 | fprintf(fp, 593 | "Examples:\n" 594 | "Turning off the DI-O device number 3 paired to the remote ID 424242:\n" 595 | " $ %s -p dio -r 424242 -d 3 -c off\n\n" 596 | "Starting a fast RF scan on every OTAX devices, sending the 'ON' command:\n" 597 | " $ %s -p otax -c on -s -n 1\n\n", 598 | argv[0], argv[0]); 599 | 600 | } 601 | 602 | static const char short_options[] = "H:p:r:d:c:sn:a:Rg:vh"; 603 | 604 | static const struct option long_options[] = { 605 | {"hw", required_argument, NULL, 'H'}, 606 | {"proto", required_argument, NULL, 'p'}, 607 | {"remote", required_argument, NULL, 'r'}, 608 | {"device", required_argument, NULL, 'd'}, 609 | {"command", required_argument, NULL, 'c'}, 610 | {"scan", no_argument, NULL, 's'}, 611 | {"nframe", required_argument, NULL, 'n'}, 612 | {"accuracy", required_argument, NULL, 'a'}, 613 | {"raw", no_argument, NULL, 'R'}, 614 | {"gpio", required_argument, NULL, 'g'}, 615 | {"verbose", required_argument, NULL, 'v'}, 616 | {"help", no_argument, NULL, 'h'}, 617 | {0, 0, 0, 0} 618 | }; 619 | 620 | int main(int argc, char **argv) 621 | { 622 | int ret = 0; 623 | int hw = -1; 624 | int nframe = -1; 625 | int protocol = -1, command = -1; 626 | uint8_t force_raw = 0; 627 | uint32_t remote_id = 0, device_id = 0; 628 | uint8_t needed_params = PARAM_PROTOCOL | PARAM_REMOTE_ID | PARAM_DEVICE_ID | PARAM_COMMAND; 629 | uint16_t provided_params = 0; 630 | uint32_t proto_first = 0, proto_last = 0, remote_first = 0, remote_last = 0, device_first = 0, device_last = 0; 631 | struct rf_hardware_params hw_params; 632 | char * p; 633 | uint32_t i, j, k; 634 | 635 | /* Parse the configuration file first */ 636 | parse_config_file(&provided_params, &hw, &hw_params); 637 | 638 | for (;;) { 639 | int index; 640 | int c; 641 | 642 | c = getopt_long(argc, argv, short_options, long_options, &index); 643 | 644 | if (-1 == c) 645 | break; 646 | 647 | switch (c) { 648 | case 0: /* getopt_long() flag */ 649 | break; 650 | 651 | case 'H': 652 | hw = strtoul(optarg, &p, 0); 653 | if (*p != '\0') { 654 | hw = get_hw_id_by_name(optarg); 655 | } 656 | 657 | if (hw < 0 || hw >= ARRAY_SIZE(hardware_drivers)) { 658 | fprintf(stderr, "Unsupported RF hardware %s\n", optarg); 659 | usage(stderr, argc, argv); 660 | return -1; 661 | } 662 | 663 | provided_params |= PARAM_HARDWARE; 664 | break; 665 | 666 | case 'p': 667 | protocol = strtoul(optarg, &p, 0); 668 | if (*p != '\0') { 669 | protocol = get_protocol_id_by_name(optarg); 670 | } 671 | 672 | if (protocol < 0 || protocol >= ARRAY_SIZE(protocol_drivers)) { 673 | fprintf(stderr, "Unsupported RF protocol %s\n", optarg); 674 | usage(stderr, argc, argv); 675 | return -1; 676 | } 677 | 678 | needed_params &= (PARAM_PROTOCOL | protocol_drivers[protocol]->needed_params); 679 | 680 | provided_params |= PARAM_PROTOCOL; 681 | break; 682 | 683 | case 'r': 684 | remote_id = strtoul(optarg, NULL, 0); 685 | provided_params |= PARAM_REMOTE_ID; 686 | break; 687 | 688 | case 'd': 689 | device_id = strtoul(optarg, NULL, 0); 690 | provided_params |= PARAM_DEVICE_ID; 691 | break; 692 | 693 | case 'c': 694 | command = strtoul(optarg, &p, 0); 695 | if (*p != '\0') { 696 | command = get_cmd_id_by_name(optarg); 697 | } 698 | 699 | if (command < 0 || command >= RF_CMD_MAX) { 700 | fprintf(stderr, "Unsupported RF command %s\n", optarg); 701 | usage(stderr, argc, argv); 702 | return -1; 703 | } 704 | 705 | provided_params |= PARAM_COMMAND; 706 | break; 707 | 708 | case 's': 709 | provided_params |= PARAM_SCAN; 710 | needed_params &= ~(PARAM_PROTOCOL | PARAM_REMOTE_ID | PARAM_DEVICE_ID); 711 | break; 712 | 713 | case 'n': 714 | nframe = strtoul(optarg, NULL, 0); 715 | provided_params |= PARAM_NFRAME; 716 | break; 717 | 718 | case 'a': 719 | raw_fallback_accuracy = strtoul(optarg, NULL, 0); 720 | if (raw_fallback_accuracy > 100) { 721 | fprintf(stderr, "Invalid accuracy %s\n", optarg); 722 | usage(stderr, argc, argv); 723 | return -1; 724 | } 725 | 726 | provided_params |= PARAM_ACCURACY; 727 | break; 728 | 729 | case 'R': 730 | provided_params |= PARAM_RAW; 731 | break; 732 | 733 | case 'g': 734 | hw_params.gpio = strtoul(optarg, &p, 0); 735 | provided_params |= PARAM_GPIO; 736 | break; 737 | 738 | case 'v': 739 | debug_level++; 740 | provided_params |= PARAM_VERBOSE; 741 | break; 742 | 743 | case 'h': 744 | usage(stdout, argc, argv); 745 | return 0; 746 | 747 | default: 748 | usage(stderr, argc, argv); 749 | return -1; 750 | } 751 | } 752 | 753 | dbg_printf(2, "Used configuration file is %s\n", CONFIG_FILE_PATH); 754 | 755 | if (needed_params & ~(provided_params)) { 756 | fprintf(stderr, "Missing arguments:"); 757 | for (i = 0; i < ARRAY_SIZE(parameter_str); i++) { 758 | if ((needed_params & ~(provided_params)) & (0x1 << i)) { 759 | fprintf(stderr, " %s", parameter_str[i]); 760 | } 761 | } 762 | fprintf(stderr, " !\n"); 763 | usage(stderr, argc, argv); 764 | return -1; 765 | } 766 | 767 | if (hw < 0) { 768 | /* Try to auto-detect */ 769 | current_hw_driver = auto_detect_hw_driver(); 770 | if (!current_hw_driver) { 771 | fprintf(stderr, "Cannot auto-detect HW driver\n"); 772 | return -1; 773 | } 774 | } else { 775 | current_hw_driver = get_hw_driver_by_id(hw); 776 | if (!current_hw_driver) { 777 | fprintf(stderr, "Cannot find HW driver with index %d\n", hw); 778 | return -1; 779 | } 780 | } 781 | 782 | hw_params.provided_params = provided_params; 783 | 784 | if (current_hw_driver->needed_hw_params & ~(provided_params)) { 785 | fprintf(stderr, "Missing arguments specific to the %s driver:", current_hw_driver->name); 786 | for (i = 0; i < ARRAY_SIZE(parameter_str); i++) { 787 | if ((current_hw_driver->needed_hw_params & ~(provided_params)) & (0x1 << i)) { 788 | fprintf(stderr, " %s", parameter_str[i]); 789 | } 790 | } 791 | fprintf(stderr, " !\n"); 792 | usage(stderr, argc, argv); 793 | return -1; 794 | } 795 | 796 | printf("Initializing %s driver...\n", current_hw_driver->long_name); 797 | ret = current_hw_driver->init(&hw_params); 798 | if (ret < 0) { 799 | fprintf(stderr, "Cannot initialize %s driver\n", current_hw_driver->name); 800 | goto exit; 801 | } 802 | 803 | if (nframe > 0) { 804 | printf("Number of frames forced to %d\n", nframe); 805 | } 806 | 807 | if (provided_params & PARAM_RAW) { 808 | if (!(current_hw_driver->supported_bit_fmts & (1 << RF_BIT_FMT_RAW))) { 809 | printf("Warning: %s does not support the RAW format, ignoring RAW conversion\n", current_hw_driver->name); 810 | } else { 811 | force_raw = 1; 812 | } 813 | } 814 | 815 | if (provided_params & PARAM_SCAN) { 816 | printf("Scanning"); 817 | 818 | if (provided_params & PARAM_DEVICE_ID) { 819 | device_first = device_last = device_id; 820 | printf(" device ID %u", device_id); 821 | } 822 | 823 | if (provided_params & PARAM_REMOTE_ID) { 824 | remote_first = remote_last = remote_id; 825 | printf(", using remote ID %u", remote_id); 826 | } 827 | 828 | if (provided_params & PARAM_PROTOCOL) { 829 | proto_first = proto_last = protocol; 830 | printf(", with protocol fixed to %s", protocol_drivers[protocol]->name); 831 | } else { 832 | proto_last = ARRAY_SIZE(protocol_drivers) - 1; 833 | } 834 | 835 | printf("...\n"); 836 | 837 | for (i = proto_first; i <= proto_last; i++) { 838 | if (nframe > 0) { 839 | protocol_drivers[i]->timings->frame_count = nframe; 840 | } 841 | 842 | if (!(provided_params & PARAM_REMOTE_ID)) { 843 | if (protocol_drivers[i]->needed_params & PARAM_REMOTE_ID) { 844 | remote_last = protocol_drivers[i]->remote_code_max; 845 | } 846 | } 847 | 848 | if (!(provided_params & PARAM_DEVICE_ID)) { 849 | if (protocol_drivers[i]->needed_params & PARAM_DEVICE_ID) { 850 | device_last = protocol_drivers[i]->device_code_max; 851 | } 852 | } 853 | 854 | for (j = remote_first; j <= remote_last; j++) { 855 | for (k = device_first; k <= device_last; k++) { 856 | send_cmd(j, k, (rf_command_t) command, i, force_raw); 857 | } 858 | } 859 | } 860 | } else { 861 | if (nframe > 0) { 862 | protocol_drivers[protocol]->timings->frame_count = nframe; 863 | } 864 | 865 | ret = send_cmd(remote_id, device_id, (rf_command_t) command, protocol, force_raw); 866 | } 867 | 868 | exit: 869 | current_hw_driver->close(); 870 | 871 | return ret; 872 | } 873 | -------------------------------------------------------------------------------- /rf-ctrl.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration file for rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | # 4 | # Copyright (C) 2018 Jean-Christophe Rona 5 | # 6 | # NOTE: The following values can be overridden using the command-line options of rf-ctrl 7 | # 8 | 9 | # Default hardware driver to use (auto detection if none) 10 | #HARDWARE = sysfs-gpio 11 | 12 | # Default GPIO to use to transmit the signal (only for hardware drivers supporting it) 13 | #GPIO = 101 14 | 15 | # Convert HL frames to RAW if possible (TRUE/FALSE) 16 | #FORCE_RAW = FALSE 17 | 18 | # Accuracy of the timings in percent when HL frames are converted to RAW (default 90%) 19 | #RAW_ACCURACY = 90 20 | -------------------------------------------------------------------------------- /rf-ctrl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * 4 | * Copyright (C) 2018 Jean-Christophe Rona 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #ifndef _RF_CTRL_H_ 22 | #define _RF_CTRL_H_ 23 | 24 | #define PARAM_HARDWARE 0x0001 25 | #define PARAM_PROTOCOL 0x0002 26 | #define PARAM_REMOTE_ID 0x0004 27 | #define PARAM_DEVICE_ID 0x0008 28 | #define PARAM_COMMAND 0x0010 29 | #define PARAM_SCAN 0X0020 30 | #define PARAM_NFRAME 0X0040 31 | #define PARAM_ACCURACY 0X0080 32 | #define PARAM_RAW 0X0100 33 | #define PARAM_GPIO 0X0200 34 | #define PARAM_VERBOSE 0X0400 35 | 36 | #define STORAGE_PATH_MAX_LEN 512 37 | 38 | typedef enum { 39 | RF_CMD_OFF = 0, 40 | RF_CMD_ON = 1, 41 | RF_CMD_GOFF = 2, 42 | RF_CMD_GON = 3, 43 | RF_CMD_PROG = 4, 44 | RF_CMD_F1 = 5, 45 | RF_CMD_F2 = 6, 46 | RF_CMD_F3 = 7, 47 | RF_CMD_MAX, 48 | } rf_command_t; 49 | 50 | typedef enum { 51 | RF_BIT_FMT_HL = 0, 52 | RF_BIT_FMT_LH = 1, 53 | RF_BIT_FMT_RAW = 2, 54 | } rf_bit_fmt_t; 55 | 56 | extern char *(rf_command_str[]); 57 | extern char *(rf_bit_fmt_str[]); 58 | 59 | struct timing_config { 60 | uint16_t start_bit_h_time; 61 | uint16_t start_bit_l_time; 62 | uint16_t end_bit_h_time; 63 | uint16_t end_bit_l_time; 64 | uint16_t data_bit0_h_time; 65 | uint16_t data_bit0_l_time; 66 | uint16_t data_bit1_h_time; 67 | uint16_t data_bit1_l_time; 68 | uint16_t base_time; 69 | rf_bit_fmt_t bit_fmt; 70 | uint8_t frame_count; 71 | }; 72 | 73 | /* List of parameters that a hardware driver might use */ 74 | struct rf_hardware_params { 75 | uint8_t gpio; 76 | uint16_t provided_params; 77 | }; 78 | 79 | struct rf_hardware_driver { 80 | char *name; 81 | char *cmd_name; 82 | char *long_name; 83 | uint8_t supported_bit_fmts; 84 | uint16_t needed_hw_params; 85 | int (*probe)(void); 86 | int (*init)(struct rf_hardware_params *params); 87 | void (*close)(void); 88 | int (*send_cmd)(struct timing_config *config, uint8_t *frame_data, uint16_t bit_count); 89 | }; 90 | 91 | struct rf_protocol_driver { 92 | char *name; 93 | char *cmd_name; 94 | struct timing_config *timings; 95 | int (*format_cmd)(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command); 96 | uint32_t remote_code_max; 97 | uint32_t device_code_max; 98 | uint16_t needed_params; 99 | }; 100 | 101 | int is_dbg_enabled(int level); 102 | void dbg_printf(int level, char *buff, ...); 103 | void get_storage_path(char *path, struct rf_protocol_driver *protocol); 104 | 105 | #endif /* _RF_CTRL_H_ */ 106 | -------------------------------------------------------------------------------- /somfy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Somfy protocol implementation (carrier frequency is 433.42MHz) 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "rf-ctrl.h" 28 | #include "raw.h" 29 | 30 | #define PROTOCOL_NAME "Somfy RTS" 31 | 32 | #define SOMFY_FRAME_COUNT 10 33 | 34 | struct rf_protocol_driver somfy_driver; 35 | 36 | 37 | static size_t write_pulses(uint8_t *buf, size_t offset, uint8_t pulses) { 38 | size_t count = offset; 39 | uint8_t i; 40 | 41 | /* Starting pulses (4 x high, 4 x low) */ 42 | for (i = 0; i < pulses; i++) { 43 | count += raw_write_high(buf, count, 4); 44 | count += raw_write_low(buf, count, 4); 45 | } 46 | 47 | /* Sync pulse (8 x high, 1 x low) */ 48 | count += raw_write_high(buf, count, 8); 49 | count += raw_write_low(buf, count, 1); 50 | 51 | return (count - offset); 52 | } 53 | 54 | static size_t write_data(uint8_t *buf, size_t offset, uint16_t rolling_code, uint8_t key, uint32_t address, uint8_t command) { 55 | size_t count = offset; 56 | uint8_t data[7]; 57 | uint8_t checksum = 0; 58 | uint8_t i; 59 | 60 | /* 61 | * | 0 | 1 | 2 3 | 4 5 6 | 62 | * | key | cmd/chksum | rolling code | address | 63 | */ 64 | 65 | data[0] = key & 0xFF; 66 | data[1] = (command << 4) & 0xF0; 67 | data[2] = (rolling_code >> 8) & 0xFF; 68 | data[3] = rolling_code & 0xFF; 69 | data[4] = address & 0xFF; 70 | data[5] = (address >> 8) & 0xFF; 71 | data[6] = (address >> 16) & 0xFF; 72 | 73 | /* Checksum */ 74 | for (i = 0; i < 7; i++) { 75 | checksum = checksum ^ data[i] ^ (data[i] >> 4); 76 | } 77 | data[1] |= checksum & 0x0F; 78 | 79 | /* Encryption */ 80 | for (i = 1; i < 7; i++) { 81 | data[i] = data[i] ^ data[i - 1]; 82 | } 83 | 84 | /* 85 | * RAW frame generation 86 | * All bit periods are the same (1 * 625 us), 0 format is LH, 1 format is HL 87 | */ 88 | count += raw_write_bits(buf, count, data, 7 * 8, RAW_EDGE_ORDER_LH, 1, 1, RAW_EDGE_ORDER_HL, 1, 1); 89 | 90 | return (count - offset); 91 | } 92 | 93 | static int somfy_format_cmd(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command) { 94 | const int bit_count = 213 + (SOMFY_FRAME_COUNT - 1) * 225; 95 | uint8_t raw_cmd; // 4 bits 96 | uint16_t rolling_code = 0; 97 | char data_file_path[STORAGE_PATH_MAX_LEN]; 98 | FILE * f; 99 | size_t count = 0; 100 | uint8_t i; 101 | 102 | if (data_len * 8 < bit_count) { 103 | fprintf(stderr, "%s: data buffer too small (%lu available, %d needed)\n", PROTOCOL_NAME, (unsigned long) data_len, (bit_count + 7)/8); 104 | return -1; 105 | } 106 | 107 | switch (command) { 108 | case RF_CMD_OFF: 109 | raw_cmd = 0x04; 110 | break; 111 | 112 | case RF_CMD_ON: 113 | raw_cmd = 0x02; 114 | break; 115 | 116 | case RF_CMD_PROG: 117 | raw_cmd = 0x08; 118 | break; 119 | 120 | case RF_CMD_F1: // My/Dim button 121 | raw_cmd = 0x01; 122 | break; 123 | 124 | default: 125 | fprintf(stderr, "%s: Unsupported command %d\n", PROTOCOL_NAME, (int) command); 126 | return -1; 127 | } 128 | 129 | if (device_code < 0x400) { 130 | printf("%s: Device ID is less than 0x400, it might not be recognized/allowed by the device\n", PROTOCOL_NAME); 131 | } 132 | 133 | /* Get the current rolling code for that device and remote */ 134 | get_storage_path(data_file_path, &somfy_driver); 135 | snprintf(data_file_path + strlen(data_file_path), STORAGE_PATH_MAX_LEN, "/%02X.%06X", remote_code, device_code); 136 | 137 | dbg_printf(3, "%s: Data file is %s\n", PROTOCOL_NAME, data_file_path); 138 | 139 | f = fopen(data_file_path, "r+"); 140 | if (f == NULL) { 141 | f = fopen(data_file_path, "w+"); 142 | if (f == NULL) { 143 | fprintf(stderr, "%s: Cannot open %s\n", PROTOCOL_NAME, data_file_path); 144 | return -1; 145 | } 146 | } 147 | 148 | if (fscanf(f, "%04hX", &rolling_code) != 1) { 149 | dbg_printf(2, "%s: Rolling code initialized to 0\n", PROTOCOL_NAME); 150 | } 151 | 152 | fseek(f, 0, SEEK_SET); 153 | fprintf(f, "%04X\n", (rolling_code + 1) % 0x10000); 154 | 155 | fclose(f); 156 | 157 | dbg_printf(1, "%s: Rolling code at %04X\n", PROTOCOL_NAME, rolling_code); 158 | 159 | /* Generate the whole frame */ 160 | for (i = 0; i < SOMFY_FRAME_COUNT; i++) { 161 | /* Starting/sync pulses */ 162 | if (i == 0) { 163 | /* First frame */ 164 | /* Big starting pulse (16 x high, 12 x low) */ 165 | count += raw_write_high(data, count, 16); 166 | count += raw_write_low(data, count, 12); 167 | 168 | /* Then 2 regular starting pulses */ 169 | count += write_pulses(data, count, 2); 170 | } else { 171 | /* Following frames (7 regular starting pulses) */ 172 | count += write_pulses(data, count, 7); 173 | } 174 | 175 | /* Actual data */ 176 | count += write_data(data, count, rolling_code, remote_code, device_code, raw_cmd); 177 | 178 | /* Gap before the next frame (48 x low = ~30ms) */ 179 | count += raw_write_low(data, count, 48); 180 | } 181 | 182 | return bit_count; 183 | } 184 | 185 | static struct timing_config somfy_timings = { 186 | .base_time = 625, // 625 us 187 | .bit_fmt = RF_BIT_FMT_RAW, 188 | .frame_count = 1, // the sequence of frames is generated as one 189 | }; 190 | 191 | struct rf_protocol_driver somfy_driver = { 192 | .name = PROTOCOL_NAME, 193 | .cmd_name = "somfy", 194 | .timings = &somfy_timings, 195 | .format_cmd = &somfy_format_cmd, 196 | .remote_code_max = 0xFF, 197 | .device_code_max = 0xFFFFFF, 198 | .needed_params = PARAM_REMOTE_ID | PARAM_DEVICE_ID | PARAM_COMMAND, 199 | }; 200 | -------------------------------------------------------------------------------- /sumtech.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * Reversed protocol of Sumtech wireless plugs 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "rf-ctrl.h" 28 | 29 | #define PROTOCOL_NAME "Sumtech" 30 | 31 | 32 | static int sumtech_format_cmd(uint8_t *data, size_t data_len, uint32_t remote_code, uint32_t device_code, rf_command_t command) { 33 | const int bit_count = 32; 34 | uint8_t raw_cmd; 35 | 36 | if (data_len * 8 < bit_count) { 37 | fprintf(stderr, "%s: data buffer too small (%lu available, %d needed)\n", PROTOCOL_NAME, (unsigned long) data_len, (bit_count + 7)/8); 38 | return -1; 39 | } 40 | 41 | switch (command) { 42 | case RF_CMD_OFF: 43 | raw_cmd = 0x00; 44 | break; 45 | 46 | case RF_CMD_ON: 47 | raw_cmd = 0x01; 48 | break; 49 | 50 | default: 51 | fprintf(stderr, "%s: Unsupported command %d\n", PROTOCOL_NAME, (int) command); 52 | return -1; 53 | } 54 | 55 | data[0] = (remote_code >> 16) & 0xFF; 56 | data[1] = (remote_code >> 8) & 0xFF; 57 | data[2] = remote_code & 0xFF; 58 | data[3] = ((raw_cmd & 0x01) << 7 ) | (device_code & 0x7F); 59 | 60 | return bit_count; 61 | } 62 | 63 | static struct timing_config sumtech_timings = { 64 | .start_bit_h_time = 4940, // 4940 us 65 | .start_bit_l_time = 9640, // 9640 us 66 | .end_bit_h_time = 0, // 0 us 67 | .end_bit_l_time = 0, // 0 us 68 | .data_bit0_h_time = 1200, // 1200 us 69 | .data_bit0_l_time = 400, // 400 us 70 | .data_bit1_h_time = 400, // 400 us 71 | .data_bit1_l_time = 1200, // 1200 us 72 | .bit_fmt = RF_BIT_FMT_LH, 73 | .frame_count = 10, 74 | }; 75 | 76 | struct rf_protocol_driver sumtech_driver = { 77 | .name = PROTOCOL_NAME, 78 | .cmd_name = "sumtech", 79 | .timings = &sumtech_timings, 80 | .format_cmd = &sumtech_format_cmd, 81 | .remote_code_max = 0xFFFFFF, 82 | .device_code_max = 0x7F, 83 | .needed_params = PARAM_REMOTE_ID | PARAM_DEVICE_ID | PARAM_COMMAND, 84 | }; 85 | -------------------------------------------------------------------------------- /sysfs-gpio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rf-ctrl - A command-line tool to control 433MHz OOK based devices 3 | * SYSFS GPIO-based 433 MHz RF Transmitter driver 4 | * 5 | * Copyright (C) 2018 Jean-Christophe Rona 6 | * 7 | * This program is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU General Public License 9 | * as published by the Free Software Foundation; either version 2 10 | * of the License, or (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "rf-ctrl.h" 33 | 34 | #define HARDWARE_NAME "SYSFS GPIO" 35 | 36 | #define GPIO_SYSFS_ROOT "/sys/class/gpio" 37 | #define GPIO_SYSFS_EXPORT "export" // At root level 38 | #define GPIO_SYSFS_UNEXPORT "unexport" // At root level 39 | #define GPIO_SYSFS_DIRECTION "direction" // At root/gpio level 40 | #define GPIO_SYSFS_VALUE "value" // At root/gpio level 41 | 42 | static uint16_t gpio_num; 43 | 44 | 45 | static int sysfs_gpio_probe(void) { 46 | /* This driver cannot be auto-detected */ 47 | return -1; 48 | } 49 | 50 | static int sysfs_gpio_init(struct rf_hardware_params *params) { 51 | FILE *f; 52 | char path[256]; 53 | 54 | if (!(params->provided_params & PARAM_GPIO)) { 55 | fprintf(stderr, "%s: GPIO parameter missing !\n", HARDWARE_NAME); 56 | return -1; 57 | } 58 | 59 | gpio_num = params->gpio; 60 | 61 | printf("%s: Using GPIO %u\n", HARDWARE_NAME, gpio_num); 62 | 63 | /* Export the GPIO used by the transmitter */ 64 | f = fopen(GPIO_SYSFS_ROOT"/"GPIO_SYSFS_EXPORT, "w"); 65 | if (f == NULL) { 66 | fprintf(stderr, "%s: Unable to open %s !\n", HARDWARE_NAME, GPIO_SYSFS_ROOT"/"GPIO_SYSFS_EXPORT); 67 | return -1; 68 | } 69 | 70 | fprintf(f, "%u\n", gpio_num); 71 | 72 | fclose(f); 73 | 74 | /* Set the direction of the GPIO as output */ 75 | snprintf(path, 256, "%s/gpio%u/%s", GPIO_SYSFS_ROOT, gpio_num, GPIO_SYSFS_DIRECTION); 76 | 77 | f = fopen(path, "w"); 78 | if (f == NULL) { 79 | fprintf(stderr, "%s: Unable to open %s !\n", HARDWARE_NAME, path); 80 | return -1; 81 | } 82 | 83 | fprintf(f, "out\n"); 84 | 85 | fclose(f); 86 | 87 | return 0; 88 | } 89 | 90 | static void sysfs_gpio_close(void) { 91 | FILE *f; 92 | 93 | /* Release the GPIO used by the transmitter */ 94 | f = fopen(GPIO_SYSFS_ROOT"/"GPIO_SYSFS_UNEXPORT, "w"); 95 | if (f == NULL) { 96 | fprintf(stderr, "%s: Unable to open %s !\n", HARDWARE_NAME, GPIO_SYSFS_ROOT"/"GPIO_SYSFS_UNEXPORT); 97 | return; 98 | } 99 | 100 | fprintf(f, "%u\n", gpio_num); 101 | 102 | fclose(f); 103 | } 104 | 105 | static int sysfs_gpio_send_cmd(struct timing_config *config, uint8_t *frame_data, uint16_t bit_count) { 106 | int fd; 107 | char path[256]; 108 | uint16_t i, j; 109 | char old_bit, new_bit; 110 | 111 | snprintf(path, 256, "%s/gpio%u/%s", GPIO_SYSFS_ROOT, gpio_num, GPIO_SYSFS_VALUE); 112 | 113 | fd = open(path, O_WRONLY| O_SYNC); 114 | if (fd < 0) { 115 | fprintf(stderr, "%s: Unable to open %s (%s) !\n", HARDWARE_NAME, path, strerror(errno)); 116 | return fd; 117 | } 118 | 119 | for (j = 0; j < config->frame_count; j++) { 120 | switch (config->bit_fmt) { 121 | case RF_BIT_FMT_HL: 122 | if (config->start_bit_h_time > 0) { 123 | write(fd, "1", 1); 124 | usleep(config->start_bit_h_time); 125 | } 126 | if (config->start_bit_l_time > 0) { 127 | write(fd, "0", 1); 128 | usleep(config->start_bit_l_time); 129 | } 130 | break; 131 | 132 | case RF_BIT_FMT_LH: 133 | if (config->start_bit_l_time > 0) { 134 | write(fd, "0", 1); 135 | usleep(config->start_bit_l_time); 136 | } 137 | if (config->start_bit_h_time > 0) { 138 | write(fd, "1", 1); 139 | usleep(config->start_bit_h_time); 140 | } 141 | break; 142 | } 143 | 144 | /* Toggle the GPIO bit by bit */ 145 | old_bit = 'N'; 146 | for (i = 0; i < bit_count; i++) { 147 | new_bit = (frame_data[i/8] & (1 << (7 - (i % 8)))) ? '1' : '0'; 148 | 149 | switch (config->bit_fmt) { 150 | case RF_BIT_FMT_HL: 151 | if (new_bit == '1') { 152 | if (config->data_bit1_h_time > 0) { 153 | write(fd, "1", 1); 154 | usleep(config->data_bit1_h_time); 155 | } 156 | if (config->data_bit1_l_time > 0) { 157 | write(fd, "0", 1); 158 | usleep(config->data_bit1_l_time); 159 | } 160 | } else { 161 | if (config->data_bit0_h_time > 0) { 162 | write(fd, "1", 1); 163 | usleep(config->data_bit0_h_time); 164 | } 165 | if (config->data_bit0_l_time > 0) { 166 | write(fd, "0", 1); 167 | usleep(config->data_bit0_l_time); 168 | } 169 | } 170 | break; 171 | 172 | case RF_BIT_FMT_LH: 173 | if (new_bit == '1') { 174 | if (config->data_bit1_l_time > 0) { 175 | write(fd, "0", 1); 176 | usleep(config->data_bit1_l_time); 177 | } 178 | if (config->data_bit1_h_time > 0) { 179 | write(fd, "1", 1); 180 | usleep(config->data_bit1_h_time); 181 | } 182 | } else { 183 | if (config->data_bit0_l_time > 0) { 184 | write(fd, "0", 1); 185 | usleep(config->data_bit0_l_time); 186 | } 187 | if (config->data_bit0_h_time > 0) { 188 | write(fd, "1", 1); 189 | usleep(config->data_bit0_h_time); 190 | } 191 | } 192 | break; 193 | 194 | case RF_BIT_FMT_RAW: 195 | if (new_bit != old_bit) { 196 | old_bit = new_bit; 197 | write(fd, &new_bit, 1); 198 | } 199 | 200 | usleep(config->base_time); 201 | break; 202 | } 203 | } 204 | 205 | switch (config->bit_fmt) { 206 | case RF_BIT_FMT_HL: 207 | if (config->end_bit_h_time > 0) { 208 | write(fd, "1", 1); 209 | usleep(config->end_bit_h_time); 210 | } 211 | if (config->end_bit_l_time > 0) { 212 | write(fd, "0", 1); 213 | usleep(config->end_bit_l_time); 214 | } 215 | break; 216 | 217 | case RF_BIT_FMT_LH: 218 | if (config->end_bit_l_time > 0) { 219 | write(fd, "0", 1); 220 | usleep(config->end_bit_l_time); 221 | } 222 | if (config->end_bit_h_time > 0) { 223 | write(fd, "1", 1); 224 | usleep(config->end_bit_h_time); 225 | } 226 | break; 227 | } 228 | } 229 | 230 | /* Make sure to turn off the transmitter */ 231 | write(fd, "0", 1); 232 | 233 | close(fd); 234 | 235 | return 0; 236 | } 237 | 238 | struct rf_hardware_driver sysfs_gpio_driver = { 239 | .name = HARDWARE_NAME, 240 | .cmd_name = "sysfs-gpio", 241 | .long_name = "SYSFS GPIO-based 433 MHz RF Transmitter", 242 | .supported_bit_fmts = (1 << RF_BIT_FMT_HL) | (1 << RF_BIT_FMT_LH) | (1 << RF_BIT_FMT_RAW), 243 | .needed_hw_params = PARAM_GPIO, 244 | .probe = &sysfs_gpio_probe, 245 | .init = &sysfs_gpio_init, 246 | .close = &sysfs_gpio_close, 247 | .send_cmd = &sysfs_gpio_send_cmd, 248 | }; 249 | --------------------------------------------------------------------------------