Repository: milaq/rpi-rf Branch: master Commit: 71fbe0507588 Files: 9 Total size: 17.7 KB Directory structure: gitextract_qi1oa1ht/ ├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── rpi_rf/ │ ├── __init__.py │ └── rpi_rf.py ├── scripts/ │ ├── rpi-rf_receive │ └── rpi-rf_send └── setup.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea dist *.egg-info *.pyc ================================================ FILE: LICENSE.txt ================================================ Copyright (c) 2016 Suat Özgür, Micha LaQua All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: MANIFEST.in ================================================ include README.rst include LICENCE.txt ================================================ FILE: README.rst ================================================ rpi-rf ====== Introduction ------------ Python module for sending and receiving 433/315MHz LPD/SRD signals with generic low-cost GPIO RF modules on a Raspberry Pi. Protocol and base logic ported ported from `rc-switch`_. Supported hardware ------------------ Most generic 433/315MHz capable modules (cost: ~2€) connected via GPIO to a Raspberry Pi. .. figure:: http://i.imgur.com/vG89UP9.jpg :alt: 433modules Compatibility ------------- Generic RF outlets and most 433/315MHz switches (cost: ~15€/3pcs). .. figure:: http://i.imgur.com/WVRxvWe.jpg :alt: rfoutlet Chipsets: * SC5262 / SC5272 * HX2262 / HX2272 * PT2262 / PT2272 * EV1527 / RT1527 / FP1527 / HS1527 For a full list of compatible devices and chipsets see the `rc-switch Wiki`_ Dependencies ------------ :: RPi.GPIO Installation ------------ On your Raspberry Pi, install the *rpi_rf* module via pip. Python 3:: # apt-get install python3-pip # pip3 install rpi-rf Wiring diagram (example) ------------------------ Raspberry Pi 1/2(B+):: RPI GPIO HEADER ____________ | ____|__ | | | | | 01| . x |02 | | . x__|________ RX | | . x__|______ | ________ | | . . | | | | | TX | ____|__x . | | |__|VCC | _______ | | __|__x . | | | | | | | | | | x____|______|____|DATA | | GND|____|__| | | . . | | | | | | | | | . . | | |DATA | | VCC|____| | | . . | | | | | | | | . . | |____|GND | | DATA|_________| | . . | |________| |_______| | . . | | . . | | . . | | . . | | . . | | . . | | . . | 39| . . |40 |_______| TX: GND > PIN 09 (GND) VCC > PIN 02 (5V) DATA > PIN 11 (GPIO17) RX: VCC > PIN 04 (5V) DATA > PIN 13 (GPIO27) GND > PIN 06 (GND) Usage ----- See `scripts`_ (`rpi-rf_send`_, `rpi-rf_receive`_) which are also shipped as cmdline tools. Open Source ----------- * The code is licensed under the `BSD Licence`_ * The project source code is hosted on `GitHub`_ * Please use `GitHub issues`_ to submit bugs and report issues .. _rc-switch: https://github.com/sui77/rc-switch .. _rc-switch Wiki: https://github.com/sui77/rc-switch/wiki .. _BSD Licence: http://www.linfo.org/bsdlicense.html .. _GitHub: https://github.com/milaq/rpi-rf .. _GitHub issues: https://github.com/milaq/rpi-rf/issues .. _scripts: https://github.com/milaq/rpi-rf/blob/master/scripts .. _rpi-rf_send: https://github.com/milaq/rpi-rf/blob/master/scripts/rpi-rf_send .. _rpi-rf_receive: https://github.com/milaq/rpi-rf/blob/master/scripts/rpi-rf_receive ================================================ FILE: rpi_rf/__init__.py ================================================ from __future__ import absolute_import from .rpi_rf import RFDevice __version__ = '0.9.7' ================================================ FILE: rpi_rf/rpi_rf.py ================================================ """ Sending and receiving 433/315Mhz signals with low-cost GPIO RF Modules on a Raspberry Pi. """ import logging import time from collections import namedtuple from RPi import GPIO MAX_CHANGES = 67 _LOGGER = logging.getLogger(__name__) Protocol = namedtuple('Protocol', ['pulselength', 'sync_high', 'sync_low', 'zero_high', 'zero_low', 'one_high', 'one_low']) PROTOCOLS = (None, Protocol(350, 1, 31, 1, 3, 3, 1), Protocol(650, 1, 10, 1, 2, 2, 1), Protocol(100, 30, 71, 4, 11, 9, 6), Protocol(380, 1, 6, 1, 3, 3, 1), Protocol(500, 6, 14, 1, 2, 2, 1), Protocol(200, 1, 10, 1, 5, 1, 1)) class RFDevice: """Representation of a GPIO RF device.""" # pylint: disable=too-many-instance-attributes,too-many-arguments def __init__(self, gpio, tx_proto=1, tx_pulselength=None, tx_repeat=10, tx_length=24, rx_tolerance=80): """Initialize the RF device.""" self.gpio = gpio self.tx_enabled = False self.tx_proto = tx_proto if tx_pulselength: self.tx_pulselength = tx_pulselength else: self.tx_pulselength = PROTOCOLS[tx_proto].pulselength self.tx_repeat = tx_repeat self.tx_length = tx_length self.rx_enabled = False self.rx_tolerance = rx_tolerance # internal values self._rx_timings = [0] * (MAX_CHANGES + 1) self._rx_last_timestamp = 0 self._rx_change_count = 0 self._rx_repeat_count = 0 # successful RX values self.rx_code = None self.rx_code_timestamp = None self.rx_proto = None self.rx_bitlength = None self.rx_pulselength = None GPIO.setmode(GPIO.BCM) _LOGGER.debug("Using GPIO " + str(gpio)) def cleanup(self): """Disable TX and RX and clean up GPIO.""" if self.tx_enabled: self.disable_tx() if self.rx_enabled: self.disable_rx() _LOGGER.debug("Cleanup") GPIO.cleanup() def enable_tx(self): """Enable TX, set up GPIO.""" if self.rx_enabled: _LOGGER.error("RX is enabled, not enabling TX") return False if not self.tx_enabled: self.tx_enabled = True GPIO.setup(self.gpio, GPIO.OUT) _LOGGER.debug("TX enabled") return True def disable_tx(self): """Disable TX, reset GPIO.""" if self.tx_enabled: # set up GPIO pin as input for safety GPIO.setup(self.gpio, GPIO.IN) self.tx_enabled = False _LOGGER.debug("TX disabled") return True def tx_code(self, code, tx_proto=None, tx_pulselength=None, tx_length=None): """ Send a decimal code. Optionally set protocol, pulselength and code length. When none given reset to default protocol, default pulselength and set code length to 24 bits. """ if tx_proto: self.tx_proto = tx_proto else: self.tx_proto = 1 if tx_pulselength: self.tx_pulselength = tx_pulselength elif not self.tx_pulselength: self.tx_pulselength = PROTOCOLS[self.tx_proto].pulselength if tx_length: self.tx_length = tx_length elif self.tx_proto == 6: self.tx_length = 32 elif (code > 16777216): self.tx_length = 32 else: self.tx_length = 24 rawcode = format(code, '#0{}b'.format(self.tx_length + 2))[2:] if self.tx_proto == 6: nexacode = "" for b in rawcode: if b == '0': nexacode = nexacode + "01" if b == '1': nexacode = nexacode + "10" rawcode = nexacode self.tx_length = 64 _LOGGER.debug("TX code: " + str(code)) return self.tx_bin(rawcode) def tx_bin(self, rawcode): """Send a binary code.""" _LOGGER.debug("TX bin: " + str(rawcode)) for _ in range(0, self.tx_repeat): if self.tx_proto == 6: if not self.tx_sync(): return False for byte in range(0, self.tx_length): if rawcode[byte] == '0': if not self.tx_l0(): return False else: if not self.tx_l1(): return False if not self.tx_sync(): return False return True def tx_l0(self): """Send a '0' bit.""" if not 0 < self.tx_proto < len(PROTOCOLS): _LOGGER.error("Unknown TX protocol") return False return self.tx_waveform(PROTOCOLS[self.tx_proto].zero_high, PROTOCOLS[self.tx_proto].zero_low) def tx_l1(self): """Send a '1' bit.""" if not 0 < self.tx_proto < len(PROTOCOLS): _LOGGER.error("Unknown TX protocol") return False return self.tx_waveform(PROTOCOLS[self.tx_proto].one_high, PROTOCOLS[self.tx_proto].one_low) def tx_sync(self): """Send a sync.""" if not 0 < self.tx_proto < len(PROTOCOLS): _LOGGER.error("Unknown TX protocol") return False return self.tx_waveform(PROTOCOLS[self.tx_proto].sync_high, PROTOCOLS[self.tx_proto].sync_low) def tx_waveform(self, highpulses, lowpulses): """Send basic waveform.""" if not self.tx_enabled: _LOGGER.error("TX is not enabled, not sending data") return False GPIO.output(self.gpio, GPIO.HIGH) self._sleep((highpulses * self.tx_pulselength) / 1000000) GPIO.output(self.gpio, GPIO.LOW) self._sleep((lowpulses * self.tx_pulselength) / 1000000) return True def enable_rx(self): """Enable RX, set up GPIO and add event detection.""" if self.tx_enabled: _LOGGER.error("TX is enabled, not enabling RX") return False if not self.rx_enabled: self.rx_enabled = True GPIO.setup(self.gpio, GPIO.IN) GPIO.add_event_detect(self.gpio, GPIO.BOTH) GPIO.add_event_callback(self.gpio, self.rx_callback) _LOGGER.debug("RX enabled") return True def disable_rx(self): """Disable RX, remove GPIO event detection.""" if self.rx_enabled: GPIO.remove_event_detect(self.gpio) self.rx_enabled = False _LOGGER.debug("RX disabled") return True # pylint: disable=unused-argument def rx_callback(self, gpio): """RX callback for GPIO event detection. Handle basic signal detection.""" timestamp = int(time.perf_counter() * 1000000) duration = timestamp - self._rx_last_timestamp if duration > 5000: if abs(duration - self._rx_timings[0]) < 200: self._rx_repeat_count += 1 self._rx_change_count -= 1 if self._rx_repeat_count == 2: for pnum in range(1, len(PROTOCOLS)): if self._rx_waveform(pnum, self._rx_change_count, timestamp): _LOGGER.debug("RX code " + str(self.rx_code)) break self._rx_repeat_count = 0 self._rx_change_count = 0 if self._rx_change_count >= MAX_CHANGES: self._rx_change_count = 0 self._rx_repeat_count = 0 self._rx_timings[self._rx_change_count] = duration self._rx_change_count += 1 self._rx_last_timestamp = timestamp def _rx_waveform(self, pnum, change_count, timestamp): """Detect waveform and format code.""" code = 0 delay = int(self._rx_timings[0] / PROTOCOLS[pnum].sync_low) delay_tolerance = delay * self.rx_tolerance / 100 for i in range(1, change_count, 2): if (abs(self._rx_timings[i] - delay * PROTOCOLS[pnum].zero_high) < delay_tolerance and abs(self._rx_timings[i+1] - delay * PROTOCOLS[pnum].zero_low) < delay_tolerance): code <<= 1 elif (abs(self._rx_timings[i] - delay * PROTOCOLS[pnum].one_high) < delay_tolerance and abs(self._rx_timings[i+1] - delay * PROTOCOLS[pnum].one_low) < delay_tolerance): code <<= 1 code |= 1 else: return False if self._rx_change_count > 6 and code != 0: self.rx_code = code self.rx_code_timestamp = timestamp self.rx_bitlength = int(change_count / 2) self.rx_pulselength = delay self.rx_proto = pnum return True return False def _sleep(self, delay): _delay = delay / 100 end = time.time() + delay - _delay while time.time() < end: time.sleep(_delay) ================================================ FILE: scripts/rpi-rf_receive ================================================ #!/usr/bin/env python3 import argparse import signal import sys import time import logging from rpi_rf import RFDevice rfdevice = None # pylint: disable=unused-argument def exithandler(signal, frame): rfdevice.cleanup() sys.exit(0) logging.basicConfig(level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S', format='%(asctime)-15s - [%(levelname)s] %(module)s: %(message)s', ) parser = argparse.ArgumentParser(description='Receives a decimal code via a 433/315MHz GPIO device') parser.add_argument('-g', dest='gpio', type=int, default=27, help="GPIO pin (Default: 27)") args = parser.parse_args() signal.signal(signal.SIGINT, exithandler) rfdevice = RFDevice(args.gpio) rfdevice.enable_rx() timestamp = None logging.info("Listening for codes on GPIO " + str(args.gpio)) while True: if rfdevice.rx_code_timestamp != timestamp: timestamp = rfdevice.rx_code_timestamp logging.info(str(rfdevice.rx_code) + " [pulselength " + str(rfdevice.rx_pulselength) + ", protocol " + str(rfdevice.rx_proto) + "]") time.sleep(0.01) rfdevice.cleanup() ================================================ FILE: scripts/rpi-rf_send ================================================ #!/usr/bin/env python3 import argparse import logging from rpi_rf import RFDevice logging.basicConfig(level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S', format='%(asctime)-15s - [%(levelname)s] %(module)s: %(message)s',) parser = argparse.ArgumentParser(description='Sends a decimal code via a 433/315MHz GPIO device') parser.add_argument('code', metavar='CODE', type=int, help="Decimal code to send") parser.add_argument('-g', dest='gpio', type=int, default=17, help="GPIO pin (Default: 17)") parser.add_argument('-p', dest='pulselength', type=int, default=None, help="Pulselength (Default: 350)") parser.add_argument('-t', dest='protocol', type=int, default=None, help="Protocol (Default: 1)") parser.add_argument('-l', dest='length', type=int, default=None, help="Codelength (Default: 24)") parser.add_argument('-r', dest='repeat', type=int, default=10, help="Repeat cycles (Default: 10)") args = parser.parse_args() rfdevice = RFDevice(args.gpio) rfdevice.enable_tx() rfdevice.tx_repeat = args.repeat if args.protocol: protocol = args.protocol else: protocol = "default" if args.pulselength: pulselength = args.pulselength else: pulselength = "default" if args.length: length = args.length else: length = "default" logging.info(str(args.code) + " [protocol: " + str(protocol) + ", pulselength: " + str(pulselength) + ", length: " + str(length) + ", repeat: " + str(rfdevice.tx_repeat) + "]") rfdevice.tx_code(args.code, args.protocol, args.pulselength, args.length) rfdevice.cleanup() ================================================ FILE: setup.py ================================================ from setuptools import setup, find_packages from os import path here = path.abspath(path.dirname(__file__)) with open(path.join(here, 'README.rst'), encoding='utf-8') as f: long_description = f.read() setup( name='rpi-rf', version='0.9.7', author='Micha LaQua', author_email='micha.laqua@gmail.com', description='Sending and receiving 433/315MHz signals with low-cost GPIO RF modules on a Raspberry Pi', long_description=long_description, url='https://github.com/milaq/rpi-rf', license='BSD', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Topic :: Software Development :: Build Tools', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 3', 'Operating System :: POSIX :: Linux', 'Topic :: Software Development :: Libraries :: Python Modules' ], keywords=[ 'rpi', 'raspberry', 'raspberry pi', 'rf', 'gpio', 'radio', '433', '433mhz', '315', '315mhz' ], install_requires=['RPi.GPIO'], scripts=['scripts/rpi-rf_send', 'scripts/rpi-rf_receive'], packages=find_packages(exclude=['contrib', 'docs', 'tests']) )