From 1b5b3d74204b4bb90ecdceb3a3e01c684cad69a6 Mon Sep 17 00:00:00 2001 From: MM Date: Thu, 8 Oct 2015 08:45:00 +0200 Subject: [PATCH] more readme, more test cases, more docstrings --- README.md | 78 ++++++++++++------ SX127x/LoRa.py | 174 ++++++++++++++++++++++++++++++++++------- SX127x/board_config.py | 2 +- VERSION | 1 + beacon.py | 38 ++++----- legacy/cad.py | 16 ++++ legacy/rcv_single.py | 3 +- lora_util.py | 5 +- rcv_cont.py | 35 +++++---- test_lora.py | 59 +++++++++++--- 10 files changed, 310 insertions(+), 101 deletions(-) create mode 100755 VERSION diff --git a/README.md b/README.md index 706ac75..4e8a561 100755 --- a/README.md +++ b/README.md @@ -7,36 +7,49 @@ LoRa spread spectrum modulation. Spread spectrum modulation has a number of intriguing features: * High interference immunity -* Up to 20dB link budget advantage +* Up to 20dBm link budget advantage (for the SX1276/7/8/9) * High Doppler shift immunity -For examples of achieved ranges see the [references](#references) below. +Links to some LoRa performance reports can be found in the [references](#references) section below. # Motivation Transceiver modules are usually interfaced with microcontroller boards such as the -[Arduino](https://www.arduino.cc/) and there are already many fine C/C++ libraries for the SX128x family on +[Arduino](https://www.arduino.cc/) and there are already many fine C/C++ libraries for the SX127x family on [github](https://github.com/search?q=sx127x) and [mbed.org](https://developer.mbed.org/search/?q=sx127x). Although C/C++ is the de facto standard for development on microcontrollers, [python](https://www.python.org) running on a [Raspberry Pi](https://www.raspberrypi.org) is becoming a viable alternative for rapid prototyping. +High level programming languages like python require a full-blown OS like Linux. (There are some exceptions like +[PyMite](https://wiki.python.org/moin/PyMite) and most notably [MicroPython](https://micropython.org).) +Using hardware capable of running Linux contradicts, to some extent, the low power specification of the SX127x family. +Therefore it is clear that in most cases this approach is useful mostly for prototyping and technology testing. + +On the other hand prototyping on a full-blown OS using high level programming languages has several advantages: +* Working prototypes can be built quickly +* Technology testing ist faster +* Proof of concept is easier to achieve +* The application development phase is reached quicker + # Hardware The transceiver module is a SX1276 based Modtronix [inAir9B](http://modtronix.com/inair9.html). -It is mounted on a prototyping board to a [Raspberry Pi](https://www.raspberrypi.org) rev 2 model B. +It is mounted on a prototyping board to a Raspberry Pi rev 2 model B. -| board pin | RaspPi GPIO | Direction | -|:-------------|:-----------:|:---------:| -| inAir9B DIO0 | GPIO 21 | IN | -| inAir9B DIO1 | GPIO 22 | IN | -| inAir9B DIO2 | GPIO 23 | IN | -| inAir9B DIO3 | GPIO 24 | IN | -| LED | GPIO 25 | OUT | +| Proto board pin | RaspPi GPIO | Direction | +|:----------------|:-----------:|:---------:| +| inAir9B DIO0 | GPIO 21 | IN | +| inAir9B DIO1 | GPIO 22 | IN | +| inAir9B DIO2 | GPIO 23 | IN | +| inAir9B DIO3 | GPIO 24 | IN | +| LED | GPIO 25 | OUT | -@todo Add picture(s) +Todo: +- [ ] Add picture(s) +- [ ] Wire the SX127x reset to a GPIO? # Code Examples @@ -71,6 +84,7 @@ class MyLoRa(LoRa): def __init__(self, verbose=False): super(MyLoRa, self).__init__(verbose) + # setup registers etc. def on_rx_done(self): payload = self.read_payload(nocheck=True) @@ -85,7 +99,7 @@ class MyLoRa(LoRa): * [spidev](https://pypi.python.org/pypi/spidev) for controlling SPI -# API Reference +# Class Reference @todo @@ -107,17 +121,40 @@ Execute `test_lora.py` to run a few unit tests. # Contributors -Please feel free to comment, report issues, or even contribute! +Please feel free to comment, report issues, or contribute! -Check out my company website [Mayer Analytics](http://mayeranalytics.com) and my private blog -[mcmayer.net](http://mcmayer.net). Follow me on twitter [@markuscmayer](https://twitter.com/markuscmayer) and +Contact me via my company website [Mayer Analytics](http://mayeranalytics.com) and my private blog +[mcmayer.net](http://mcmayer.net). + +Follow me on twitter [@markuscmayer](https://twitter.com/markuscmayer) and [@mayeranalytics](https://twitter.com/mayeranalytics). -# Version +# Version and future plans **pySX127x** is still in the development phase. The current version is 0.1. +95% of functions for the Sx127x LoRa capabilities are implemented. Functions will be added when necessary. +The test coverage is rather low but we intend to change that soon. + + +# References + +### Hardware references +* [Semtech SX1276/77/78/79 - 137 MHz to 1020 MHz Low Power Long Range Transceiver](http://www.semtech.com/images/datasheet/sx1276_77_78_79.pdf) +* [Modtronix inAir9](http://modtronix.com/inair9.html) +* [Spidev Documentation](http://tightdev.net/SpiDev_Doc.pdf) +* [Make: Tutorial: Raspberry Pi GPIO Pins and Python](http://makezine.com/projects/tutorial-raspberry-pi-gpio-pins-and-python/) + +### LoRa performance tests +* [Extreme Range Links: LoRa 868 / 900MHz SX1272 LoRa module for Arduino, Raspberry Pi and Intel Galileo](https://www.cooking-hacks.com/documentation/tutorials/extreme-range-lora-sx1272-module-shield-arduino-raspberry-pi-intel-galileo/) +* [UK LoRa versus FSK - 40km LoS (Line of Sight) test!](http://www.instructables.com/id/Introducing-LoRa-/step17/Other-region-tests/) + +### Spread spectrum modulation theory +* [An Introduction to Spread Spectrum Techniques](http://www.ausairpower.net/OSR-0597.html) +* [Theory of Spread-Spectrum Communications-A Tutorial](http://www.fer.unizg.hr/_download/repository/Theory%20of%20Spread-Spectrum%20Communications-A%20Tutorial.pdf) +(technical paper) + # License @@ -132,9 +169,4 @@ but **WITHOUT ANY WARRANTY**; without even the implied warranty of GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with **pySX127x**. If not, see . - - -# References -### Range Experiments -* [Extreme Range Links: LoRa 868 / 900MHz SX1272 LoRa module for Arduino, Raspberry Pi and Intel Galileo](https://www.cooking-hacks.com/documentation/tutorials/extreme-range-lora-sx1272-module-shield-arduino-raspberry-pi-intel-galileo/) +along with **pySX127x**. If not, see . \ No newline at end of file diff --git a/SX127x/LoRa.py b/SX127x/LoRa.py index ce2499f..4b77a8a 100755 --- a/SX127x/LoRa.py +++ b/SX127x/LoRa.py @@ -24,10 +24,11 @@ def set_bit(v, index, bit): """ Set the index:th bit of v to x, and return the new value. - :param v: The integer to set the bit in + :param v: The integer to set the bit in + :type v: int :param index: 0-based index :param bit: bit to set (0 or 1) - :return: Changed integer + :return: Changed integer """ mask = 1 << index v &= ~mask @@ -101,6 +102,8 @@ def on_payload_crc_error(self): def on_fhss_change_channel(self): pass + # Internal callbacks for GPIO.add_event_detect + def _dio0(self, channel): # DIO0 00: RxDone # DIO0 01: TxDone @@ -148,12 +151,21 @@ def _dio3(self, channel): else: raise RuntimeError("unknown dio3 mapping!") + # All the set/get/read/write functions + def get_mode(self): + """ Get the mode + :return: New mode + """ self.mode = self.spi.xfer([REG.OP_MODE, 0])[1] return self.mode def set_mode(self, mode): - # the mode is backe up in self.mode + """ + :param mode: Set the mode. Use constants.MODE class + :return: New mode + """ + # the mode is backed up in self.mode if mode == self.mode: return mode if self.verbose: @@ -162,22 +174,35 @@ def set_mode(self, mode): return self.spi.xfer([REG.OP_MODE | 0x80, mode])[1] def write_payload(self, payload): + """ Get FIFO ready for TX: Set FifoAddrPtr to FifoTxBaseAddr. The transceiver is put into STDBY mode. + :param payload: Payload to write (list) + :return: Written payload + """ self.set_mode(MODE.STDBY) base_addr = self.get_fifo_tx_base_addr() self.set_fifo_addr_ptr(base_addr) return self.spi.xfer([REG.FIFO | 0x80] + payload)[1:] def reset_ptr_rx(self): - """ Get FIFO ready for RX: Set FifoAddrPtr to FifoRxBaseAddr """ + """ Get FIFO ready for RX: Set FifoAddrPtr to FifoRxBaseAddr. The transceiver is put into STDBY mode. """ self.set_mode(MODE.STDBY) base_addr = self.get_fifo_rx_base_addr() self.set_fifo_addr_ptr(base_addr) def rx_is_good(self): + """ Check the IRQ flags for RX errors + :return: True if no errors + :rtype: bool + """ flags = self.get_irq_flags() return not any([flags[s] for s in ['valid_header', 'crc_error', 'rx_done', 'rx_timeout']]) def read_payload(self , nocheck = False): + """ Read the payload from FIFO + :param nocheck: If True then check rx_is_good() + :return: Payload + :rtype: list[int] + """ if not nocheck and not self.rx_is_good(): return None rx_nb_bytes = self.get_rx_nb_bytes() @@ -187,13 +212,21 @@ def read_payload(self , nocheck = False): return payload def get_freq(self): - # returns freq in MHz + """ Get the frequency (MHz) + :return: Frequency in MHz + :rtype: float + """ msb, mid, lsb = self.spi.xfer([REG.FR_MSB, 0, 0, 0])[1:] f = lsb + 256*(mid + 256*msb) return f / 16384. def set_freq(self, f): - # f is a float in MHz + """ Set the frequency (MHz) + :param f: Frequency in MHz + "type f: float + :return: New register settings (3 bytes [msb, mid, lsb]) + :rtype: list[int] + """ assert self.mode == MODE.SLEEP or self.mode == MODE.STDBY i = int(f * 16384.) # choose floor msb = i // 65536 @@ -201,7 +234,7 @@ def set_freq(self, f): mid = i // 256 i -= mid * 256 lsb = i - self.spi.xfer([REG.FR_MSB | 0x80, msb, mid, lsb]) + return self.spi.xfer([REG.FR_MSB | 0x80, msb, mid, lsb]) def get_pa_config(self, convert_dBm=False): v = self.spi.xfer([REG.PA_CONFIG, 0])[1] @@ -218,7 +251,8 @@ def get_pa_config(self, convert_dBm=False): ) def set_pa_config(self, pa_select=None, max_power=None, output_power=None): - assert pa_select is None or pa_select == 0 # the inAir9 uses the RFO pin, so I don't allow to switch to PA_BOOST + # the inAir9 uses the RFO pin, so I don't allow to switch to PA_BOOST + assert pa_select is None or pa_select == 0 loc = locals() current = self.get_pa_config() loc = {s: current[s] if loc[s] is None else loc[s] for s in loc} @@ -323,8 +357,9 @@ def get_irq_flags_mask(self): cad_detected = v >> 0 & 0x01, ) - def set_irq_flags_mask(self, rx_timeout=None, rx_done=None, crc_error=None, valid_header=None, tx_done=None, - cad_done=None, fhss_change_ch=None, cad_detected=None): + def set_irq_flags_mask(self, + rx_timeout=None, rx_done=None, crc_error=None, valid_header=None, tx_done=None, + cad_done=None, fhss_change_ch=None, cad_detected=None): loc = locals() v = self.spi.xfer([REG.IRQ_FLAGS_MASK, 0])[1] for i, s in enumerate(['cad_detected', 'fhss_change_ch', 'cad_done', 'tx_done', 'valid_header', @@ -347,8 +382,9 @@ def get_irq_flags(self): cad_detected = v >> 0 & 0x01, ) - def set_irq_flags(self, rx_timeout=None, rx_done=None, crc_error=None, valid_header=None, tx_done=None, - cad_done=None, fhss_change_ch=None, cad_detected=None): + def set_irq_flags(self, + rx_timeout=None, rx_done=None, crc_error=None, valid_header=None, tx_done=None, + cad_done=None, fhss_change_ch=None, cad_detected=None): v = self.spi.xfer([REG.IRQ_FLAGS, 0])[1] for i, s in enumerate(['cad_detected', 'fhss_change_ch', 'cad_done', 'tx_done', 'valid_header', 'crc_error', 'rx_done', 'rx_timeout']): @@ -555,49 +591,133 @@ def set_sync_word(self, sync_word): @Getter(REG.DIO_MAPPING_1) def get_dio_mapping_1(self, mapping): - self.dio_mapping = [mapping>>6 & 0x03, mapping>>4 & 0x03, mapping>>2 & 0x03, mapping>>0 & 0x03] + self.dio_mapping[4:6] - return mapping + """ Get mapping of pins DIO0 to DIO3. Object variable dio_mapping will be set. + :param mapping: Register value + :type mapping: int + :return: Value of the mapping list + :rtype: list[int] + """ + self.dio_mapping = [mapping>>6 & 0x03, mapping>>4 & 0x03, mapping>>2 & 0x03, mapping>>0 & 0x03] \ + + self.dio_mapping[4:6] + return self.dio_mapping @Setter(REG.DIO_MAPPING_1) def set_dio_mapping_1(self, mapping): - self.dio_mapping = [mapping>>6 & 0x03, mapping>>4 & 0x03, mapping>>2 & 0x03, mapping>>0 & 0x03] + self.dio_mapping[4:6] + """ Set mapping of pins DIO0 to DIO3. Object variable dio_mapping will be set. + :param mapping: Register value + :type mapping: int + :return: New value of the register + :rtype: int + """ + self.dio_mapping = [mapping>>6 & 0x03, mapping>>4 & 0x03, mapping>>2 & 0x03, mapping>>0 & 0x03] \ + + self.dio_mapping[4:6] return mapping @Getter(REG.DIO_MAPPING_2) def get_dio_mapping_2(self, mapping): + """ Get mapping of pins DIO4 to DIO5. Object variable dio_mapping will be set. + :param mapping: Register value + :type mapping: int + :return: Value of the mapping list + :rtype: list[int] + """ self.dio_mapping = self.dio_mapping[0:4] + [mapping>>6 & 0x03, mapping>>4 & 0x03] - return mapping + return self.dio_mapping @Setter(REG.DIO_MAPPING_2) def set_dio_mapping_2(self, mapping): + """ Set mapping of pins DIO4 to DIO5. Object variable dio_mapping will be set. + :param mapping: Register value + :type mapping: int + :return: New value of the register + :rtype: int + """ assert mapping & 0b00001110 == 0 self.dio_mapping = self.dio_mapping[0:4] + [mapping>>6 & 0x03, mapping>>4 & 0x03] return mapping + def get_dio_mapping(self): + """ Utility function that returns the list of current DIO mappings. Object variable dio_mapping will be set. + :return: List of current DIO mappings + :rtype: list[int] + """ + self.get_dio_mapping_1() + return self.get_dio_mapping_2() + + def set_dio_mapping(self, mapping): + """ Utility function that returns the list of current DIO mappings. Object variable dio_mapping will be set. + :param mapping: DIO mapping list + :type mapping: list[int] + :return: New DIO mapping list + :rtype: list[int] + """ + mapping_1 = (mapping[0] & 0x03) << 6 | (mapping[1] & 0x03) << 4 | (mapping[2] & 0x3) << 2 | mapping[3] & 0x3 + mapping_2 = (mapping[4] & 0x03) << 6 | (mapping[5] & 0x03) << 4 + self.set_dio_mapping_1(mapping_1) + return self.set_dio_mapping_2(mapping_2) + @Getter(REG.VERSION) def get_version(self, version): + """ Version code of the chip. + Bits 7-4 give the full revision number; bits 3-0 give the metal mask revision number. + :return: Version code + :rtype: int + """ return version @Getter(REG.TCXO) def get_tcxo(self, tcxo): - return tcxo + """ Get TCXO or XTAL input setting + 0 -> "XTAL": Crystal Oscillator with external Crystal + 1 -> "TCXO": External clipped sine TCXO AC-connected to XTA pin + :param tcxo: 1=TCXO or 0=XTAL input setting + :return: TCXO or XTAL input setting + :type: int (0 or 1) + """ + return tcxo & 0b00010000 @Setter(REG.TCXO) def set_tcxo(self, tcxo): - assert tcxo & 0b11101111 == 0b00001001 - return tcxo + """ Make TCXO or XTAL input setting. + 0 -> "XTAL": Crystal Oscillator with external Crystal + 1 -> "TCXO": External clipped sine TCXO AC-connected to XTA pin + :param tcxo: 1=TCXO or 0=XTAL input setting + :return: new TCXO or XTAL input setting + """ + return (tcxo >= 1) << 4 | 0x09 # bits 0-3 must be 0b1001 @Getter(REG.PA_DAC) def get_pa_dac(self, pa_dac): - return pa_dac + """ Enables the +20dBm option on PA_BOOST pin + False -> Default value + True -> +20dBm on PA_BOOST when OutputPower=1111 + :return: True/False if +20dBm option on PA_BOOST on/off + :rtype: bool + """ + pa_dac &= 0x07 # only bits 0-2 + if pa_dac == 0x04: + return False + elif pa_dac == 0x07: + return True + else: + raise RuntimeError("Bad PA_DAC value %s" % hex(pa_dac)) @Setter(REG.PA_DAC) def set_pa_dac(self, pa_dac): - assert pa_dac & 0b1111100 == 0b00010000 - return pa_dac + """ Enables the +20dBm option on PA_BOOST pin + False -> Default value + True -> +20dBm on PA_BOOST when OutputPower=1111 + :param pa_dac: 1/0 if +20dBm option on PA_BOOST on/off + :return: New pa_dac register value + :rtype: int + """ + return 0x87 if pa_dac else 0x84 def dump_registers(self): - # returns a list of [reg_addr, reg_name, reg_value] tuples + """ Returns a list of [reg_addr, reg_name, reg_value] tuples. Chip is put into mode SLEEP. + :return: List of [reg_addr, reg_name, reg_value] tuples + :rtype: list[tuple] + """ self.set_mode(MODE.SLEEP) values = self.get_all_registers() skip_set = set([REG.FIFO]) @@ -678,17 +798,15 @@ def __str__(self): s += " detect_optimize %#02x\n" % self.get_detect_optimize() s += " detection_thresh %#02x\n" % self.get_detection_threshold() s += " sync_word %#02x\n" % self.get_sync_word() - s += " dio_mapping_1 %#02x\n" % self.get_dio_mapping_1() - s += " dio_mapping_2 %#02x\n" % self.get_dio_mapping_2() - s += " tcxo %#02x\n" % self.get_tcxo() - s += " pa_dac %#02x\n" % self.get_pa_dac() + s += " dio_mapping 0..5 %s\n" % self.get_dio_mapping() + s += " tcxo %s\n" % ['XTAL', 'TCXO'][self.get_tcxo()] + s += " pa_dac %s\n" % ['default', 'PA_BOOST'][self.get_pa_dac()] s += " fifo_addr_ptr %#02x\n" % self.get_fifo_addr_ptr() s += " fifo_tx_base_addr %#02x\n" % self.get_fifo_tx_base_addr() s += " fifo_rx_base_addr %#02x\n" % self.get_fifo_rx_base_addr() s += " fifo_rx_curr_addr %#02x\n" % self.get_fifo_rx_current_addr() s += " fifo_rx_byte_addr %#02x\n" % self.get_fifo_rx_byte_addr() s += " status %s\n" % self.get_modem_status() - s += " dio_mapping %s\n" % self.dio_mapping s += " version %#02x\n" % self.get_version() return s diff --git a/SX127x/board_config.py b/SX127x/board_config.py index e4efe2e..4bbbd78 100755 --- a/SX127x/board_config.py +++ b/SX127x/board_config.py @@ -38,7 +38,7 @@ def setup(): GPIO.output(BOARD.LED, 0) # DIOx for gpio_pin in [BOARD.DIO0, BOARD.DIO1, BOARD.DIO2, BOARD.DIO3]: - GPIO.setup(gpio_pin, GPIO.IN) + GPIO.setup(gpio_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) @staticmethod def led_on(value=1): diff --git a/VERSION b/VERSION new file mode 100755 index 0000000..ceab6e1 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1 \ No newline at end of file diff --git a/beacon.py b/beacon.py index c3aa72a..6bc941e 100755 --- a/beacon.py +++ b/beacon.py @@ -28,60 +28,61 @@ parser = LoRaArgumentParser("A simple LoRa beacon") parser.add_argument('--single', '-S', dest='single', default=False, action="store_true", help="Single transmission") -parser.add_argument('--wait', '-w', dest='wait', default=0, action="store", type=float, help="Waiting time between transmissions (default is 0s)") +parser.add_argument('--wait', '-w', dest='wait', default=1, action="store", type=float, help="Waiting time between transmissions (default is 0s)") class LoRaBeacon(LoRa): def __init__(self, verbose=False): super(LoRaBeacon, self).__init__(verbose) + self.set_mode(MODE.SLEEP) + self.set_dio_mapping([1,0,0,0,0,0]) def on_rx_done(self): print "\nRxDone" - print lora.get_irq_flags() - print map(hex, lora.read_payload(nocheck=True)) - lora.set_mode(MODE.SLEEP) - lora.reset_ptr_rx() - lora.set_mode(MODE.RXCONT) + print self.get_irq_flags() + print map(hex, self.read_payload(nocheck=True)) + self.set_mode(MODE.SLEEP) + self.reset_ptr_rx() + self.set_mode(MODE.RXCONT) def on_tx_done(self): print "\nTxDone" - print lora.get_irq_flags() + print self.get_irq_flags() def on_cad_done(self): print "\non_CadDone"; - print lora.get_irq_flags() + print self.get_irq_flags() def on_rx_timeout(self): print "\non_RxTimeout" - print lora.get_irq_flags() + print self.get_irq_flags() def on_valid_header(self): print "\non_ValidHeader" - print lora.get_irq_flags() + print self.get_irq_flags() def on_payload_crc_error(self): print "\non_PayloadCrcError" - print lora.get_irq_flags() + print self.get_irq_flags() def on_fhss_change_channel(self): print "\non_FhssChangeChannel" - print lora.get_irq_flags() + print self.get_irq_flags() def start(self): global args - self.set_dio_mapping_1(0b01000000) counter = 0 while True: - lora.write_payload([0x0f]) + self.write_payload([0x0f]) BOARD.led_on() - lora.set_mode(MODE.TX) + self.set_mode(MODE.TX) counter += 1 sys.stdout.flush() sys.stdout.write("\rtx #%d" % counter) tx_done = False while not tx_done: - tx_done = lora.get_irq_flags()['tx_done'] + tx_done = self.get_irq_flags()['tx_done'] BOARD.led_off() if args.single: break @@ -107,8 +108,9 @@ def start(self): #assert(lora.get_lna()['lna_gain'] == GAIN.NOT_USED) assert(lora.get_agc_auto_on() == 1) -print "simple_beacon parameters:" -print " Wait\t%f s" % args.wait +print "Beacon config:" +print " Wait %f s" % args.wait +print " Single tx = %s" % args.single print raw_input("Press enter to start...") diff --git a/legacy/cad.py b/legacy/cad.py index 165dd53..4a87673 100755 --- a/legacy/cad.py +++ b/legacy/cad.py @@ -3,6 +3,22 @@ # beacon.py # awaits transmissions in RXSINGLE mode +# This file is part of pySX127x. +# +# pySX127x is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pySX127x is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pySX127x. If not, see . + + import sys from time import sleep import SX127x diff --git a/legacy/rcv_single.py b/legacy/rcv_single.py index 7c17791..ac46fb7 100755 --- a/legacy/rcv_single.py +++ b/legacy/rcv_single.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2.7 # This file is part of pySX127x. # @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with pySX127x. If not, see . + from time import sleep from SX127x.LoRa import * from SX127x.board_config import BOARD diff --git a/lora_util.py b/lora_util.py index 0e2a0d0..3130fb6 100755 --- a/lora_util.py +++ b/lora_util.py @@ -1,7 +1,6 @@ -#!/usr/bin/env python - -""" This is utility script for the LoRa. It dumps all registers. """ +#!/usr/bin/env python2.7 +""" This is a utility script for the SX127x (LoRa mode). It dumps all registers. """ # This file is part of pySX127x. # diff --git a/rcv_cont.py b/rcv_cont.py index 2c03f75..f809b80 100755 --- a/rcv_cont.py +++ b/rcv_cont.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2.7 """ A simple continuous receiver class. """ @@ -18,7 +18,6 @@ # along with pySX127x. If not, see . -import sys from time import sleep from SX127x.LoRa import * from SX127x.LoRaArgumentParser import LoRaArgumentParser @@ -32,48 +31,50 @@ class LoRaRcvCont(LoRa): def __init__(self, verbose=False): super(LoRaRcvCont, self).__init__(verbose) + self.set_mode(MODE.SLEEP) + self.set_dio_mapping([0] * 6) def on_rx_done(self): BOARD.led_on() print "\nRxDone" - print lora.get_irq_flags() - print map(hex, lora.read_payload(nocheck=True)) - lora.set_mode(MODE.SLEEP) - lora.reset_ptr_rx() + print self.get_irq_flags() + print map(hex, self.read_payload(nocheck=True)) + self.set_mode(MODE.SLEEP) + self.reset_ptr_rx() BOARD.led_off() - lora.set_mode(MODE.RXCONT) + self.set_mode(MODE.RXCONT) def on_tx_done(self): print "\nTxDone" - print lora.get_irq_flags() + print self.get_irq_flags() def on_cad_done(self): print "\non_CadDone"; - print lora.get_irq_flags() + print self.get_irq_flags() def on_rx_timeout(self): print "\non_RxTimeout" - print lora.get_irq_flags() + print self.get_irq_flags() def on_valid_header(self): print "\non_ValidHeader" - print lora.get_irq_flags() + print self.get_irq_flags() def on_payload_crc_error(self): print "\non_PayloadCrcError" - print lora.get_irq_flags() + print self.get_irq_flags() def on_fhss_change_channel(self): print "\non_FhssChangeChannel" - print lora.get_irq_flags() + print self.get_irq_flags() def start(self): - lora.reset_ptr_rx() - lora.set_mode(MODE.RXCONT) + self.reset_ptr_rx() + self.set_mode(MODE.RXCONT) while True: sleep(.5) - rssi_value = lora.get_rssi_value() - status = lora.get_modem_status() + rssi_value = self.get_rssi_value() + status = self.get_modem_status() sys.stdout.flush() sys.stdout.write("\r%d %d %d" % (rssi_value, status['rx_ongoing'], status['modem_clear'])) diff --git a/test_lora.py b/test_lora.py index 77217c5..b03f087 100755 --- a/test_lora.py +++ b/test_lora.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2.7 """ This script runs a small number of unit tests. """ @@ -32,8 +32,13 @@ def get_reg(reg_addr): return lora.get_register(reg_addr) -def SaveState(reg_addr, len=1): - """ This decorator wraps a get/set_register around the function (unittest) call. """ +def SaveState(reg_addr, n_registers=1): + """ This decorator wraps a get/set_register around the function (unittest) call. + :param reg_addr: Start of register addresses + :param n_registers: Number of registers to save. (Useful for MSB/LSB register pairs, etc.) + :return: + """ + def decorator(func): def wrapper(self): reg_bkup = lora.get_register(reg_addr) @@ -58,6 +63,7 @@ def test_mode(self): lora.set_mode(m) self.assertEqual(lora.get_mode(), m) + @SaveState(REG.FR_MSB, n_registers=3) def test_set_freq(self): freq = lora.get_freq() for f in [433.5, 434.5, 434.0, freq]: @@ -78,13 +84,46 @@ def test_set_low_data_rate_optim(self): lora.set_low_data_rate_optim(False) self.assertEqual((get_reg(REG.MODEM_CONFIG_3) & 0b1000) >> 3, 0) - def test_set_lna_gain(self): - bkup_lna_gain = lora.get_lna()['lna_gain'] - for target_gain in [GAIN.NOT_USED, GAIN.G1, GAIN.G2, GAIN.G6, GAIN.NOT_USED, bkup_lna_gain]: - print target_gain - lora.set_lna_gain(target_gain) - actual_gain = lora.get_lna()['lna_gain'] - self.assertEqual(GAIN.lookup[actual_gain], GAIN.lookup[target_gain]) + @SaveState(REG.DIO_MAPPING_1, 2) + def test_set_dio_mapping(self): + + dio_mapping = [1] * 6 + lora.set_dio_mapping(dio_mapping) + self.assertEqual(get_reg(REG.DIO_MAPPING_1), 0b01010101) + self.assertEqual(get_reg(REG.DIO_MAPPING_2), 0b01010000) + self.assertEqual(lora.get_dio_mapping(), dio_mapping) + + dio_mapping = [2] * 6 + lora.set_dio_mapping(dio_mapping) + self.assertEqual(get_reg(REG.DIO_MAPPING_1), 0b10101010) + self.assertEqual(get_reg(REG.DIO_MAPPING_2), 0b10100000) + self.assertEqual(lora.get_dio_mapping(), dio_mapping) + + dio_mapping = [0] * 6 + lora.set_dio_mapping(dio_mapping) + self.assertEqual(get_reg(REG.DIO_MAPPING_1), 0b00000000) + self.assertEqual(get_reg(REG.DIO_MAPPING_2), 0b00000000) + self.assertEqual(lora.get_dio_mapping(), dio_mapping) + + dio_mapping = [0,1,2,0,1,2] + lora.set_dio_mapping(dio_mapping) + self.assertEqual(get_reg(REG.DIO_MAPPING_1), 0b00011000) + self.assertEqual(get_reg(REG.DIO_MAPPING_2), 0b01100000) + self.assertEqual(lora.get_dio_mapping(), dio_mapping) + + dio_mapping = [1,2,0,1,2,0] + lora.set_dio_mapping(dio_mapping) + self.assertEqual(get_reg(REG.DIO_MAPPING_1), 0b01100001) + self.assertEqual(get_reg(REG.DIO_MAPPING_2), 0b10000000) + self.assertEqual(lora.get_dio_mapping(), dio_mapping) + +# def test_set_lna_gain(self): +# bkup_lna_gain = lora.get_lna()['lna_gain'] +# for target_gain in [GAIN.NOT_USED, GAIN.G1, GAIN.G2, GAIN.G6, GAIN.NOT_USED, bkup_lna_gain]: +# print target_gain +# lora.set_lna_gain(target_gain) +# actual_gain = lora.get_lna()['lna_gain'] +# self.assertEqual(GAIN.lookup[actual_gain], GAIN.lookup[target_gain]) if __name__ == '__main__': unittest.main()