# This 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, or (at your option)
# any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
# Notes from EP2763321A1, see https://www.google.com/patents/EP2763321A1
# General
# One chirp is one symbol
# The phase stays the same during a chirp, but can change between symbols (modulated chirps)
# Chirp duration may vary
# SF: 12 = N
# CR: 4/5
# BW: 125
# Modulation
# The value of the cyclic shift can be coded over log2 N bits, noted PPM.
# When a reduced set is used (N is small), the Gray mapper sets the least significant bits to zero. For a reduced set in which two bits are set to zero, hence using only one quarter of the available symbols, we have PPM = log2 N - 2, for example.
# The errors of ± 1 in the demodulated position have a much higher probability of occurring. Hence, there is a higher error probability for least significant bits: bit0 sees twice as much errors as bit1, which sees twice as much as bit2, and so on.
# See https://patentimages.storage.googleapis.com/EP2763321A1/imgf0002.png
# Each symbol is a chirp, and a number represents a codeword. M is one modulated bit (cycle shift)
# Sync
# In the example shown, the receiver looks for the presence of 3 consecutive symbols: unmodulated, modulated with first value, modulated with second value (for example a first chirp with a modulation value of 4, and a second one with the opposite modulation N - 4). Since the reception errors are mostly ± 1 offsets, it would not be advisable choosing these values for the frame synchronisation symbols. Modulation values are predetermined.
# According to another aspect of the invention, the preamble includes preferably frequency synchronisation symbols 413 that consist in one or more, preferably two chirps that are complex-conjugate of the base unmodulated chirp. One can regard these in the baseband representation as down-chirps
# Interesting remark in [0045]
# To let the receiver align in time, a silence 420 is inserted after the symbols 413.
# Finetuning described in [0050]
# PHDR
# The header part of the frame is a data field 415 that describes how to decode the information bits. The header content determines the decoding of large sections of the message and, therefore, it is preferably modulated with a reduced set of cyclic shifts, for example one possible position every four positions, i.e. PPM = log(N)-2. Since the most likely demodulation errors are ± 1 modulation position offset, this reduces significantly the probability that the header is not correctly decoded. Different reduction factors are also possible and included in the scope of the invention. A reduced set which is one third of the total set is also feasible, but this would not give an integer number of bits per symbol when the symbol size is, as it is usually a power of two. This technique of encoding part of the chirps with a reduced set of cyclic shifts can be used in other sensitive part of the transmitted data, whenever the likelihood of decoding errors is needed to be reduced.
# Preferably, the header 415 is encoded using the lowest coding rate, RDD=4, so as to give best protection, and comprises a header CRC to check header data integrity.
# The header 415 can include all sort of data useful to decode the payload. According to a possible implementation of the invention, the header fields are coding rate, enable of payload CRC, payload length, CRC, a burst mode indication, a compressed mode indication, some reserved bits, and a ranging bit. When the ranging bit is set, the header data has different meaning except header CRC.
# Pilot symbols
# According to an aspect of the invention, with an aim of improving the robustness without sacrificing the data rate, the payload includes "pilot symbols" as illustrated in Figure 7. These are symbols which use reduced modulation set, within the payload. In the represented example, in which the coding rate is
# 4/5, symbols form coding groups 512 containing 5 adjacent symbols (see next). One every 4 such groups can be encoded with a reduced modulation set 511, i.e. 5 symbols every 20 symbols. This is enough for a simple receiver to track timing, without compromising data rate. The repetition rate of such pilot symbols is a configuration known by both receiver and transmitter and is, for example, made known to the receiver by an appropriate field in the header.
importnumpy
importstruct
fromgnuradioimportgr
fromscipyimportsignal
DELAY_AFTER_SYNC=268# Delay window after two downchirps in the LoRa signal
EPSILON=0.001
VERBOSITY=1
# Conditional print
defprintd(string,level=2):
ifVERBOSITY>=level:
print(string)
# Convert Gray code to binary
defungray(gray):
code_len=len(gray)
gray=int(gray,2)
mask=gray>>1
binary=gray
whilemask!=0:
binary=binary^mask
mask=mask>>1
binary_str=bin(binary)[2:]
return"0"*(code_len-len(binary_str))+binary_str
# XOR binary number with PRNG byte and return binary
defxor_byte(number,prng):# "number" should be a binary string, prng an integer from 0 - 255
number_int=int(number,2)
binary=number_int^prng
binary_str=bin(binary)[2:]
return"0"*(8-len(binary_str))+binary_str
# xor_byte for a bitstring
defxor_string(string,prng):
t=""
count=0
foriinrange(0,len(string),8):
t+=xor_byte(string[i:i+8],prng[count])
count+=1
returnt
# Binary to Gray code
defgray(gray):
code_len=len(gray)
gray=int(gray,2)
binary=(gray>>1)^gray
binary_str=bin(binary)[2:]
return"0"*(code_len-len(binary_str))+binary_str
# Binary string to bytes
defbin_to_bytes(bin_str):
result=b""
foriinrange(0,len(bin_str),8):
result+=bytes(bytearray([int(bin_str[i:i+8],2)]))
returnresult
# Bytes to binary string
defbytes_to_bin(byte_str):
result=''
binary_str=''
foreleminbyte_str:
binary_str=bin(elem)[2:]
binary_str="0"*(8-len(binary_str))+binary_str
result+=binary_str
returnresult
# Binary string to hex string
defbin_to_hex(bin_str,manchester=False):
str_len=len(bin_str)
ifstr_len%8!=0:
printd("Padding string of len "+str(str_len))
#bin_str = ("0" * (str_len % 8)) + bin_str # Pad at begin
bin_str=bin_str+("0"*(str_len%8))# Pad at end
ifmanchester:
t=""
foriinrange(0,str_len,2):
substr=bin_str[i:i+2]
ifsubstr=="10":
t+="1"
elifsubstr=="01":
t+="0"
else:
print("Warning: not Manchester. Decoded value will be wrong")
# 2. Apply Gray code to index of the transient of the change in instantaneous frequency
# 3. Read 4 + RDD (redundancy bits) symbols
# 4. Deinterleave
# 5. De-whiten
# 6. Hamming decode
defdemodulate(self,samples,is_hdr,out,out_w):
# Possibility 1: convolve in freq domain
#mult = numpy.convolve(...)
# Possibility 2: multiply and FFT
#mult = numpy.multiply(samples, self.downchirp)
#xx = numpy.array(samples)
#xx.tofile("/tmp/in1")
#self.downchirp.tofile("/tmp/downchirp")
#mult.tofile("/tmp/mult")
#mult = numpy.fft.fft(mult, self.window_size)
#mult = numpy.abs(mult)
# Possibility 3: just use gradient of upchirp (faster?)
gradient_mag=self.gradient(samples)
result=(int(numpy.argmax(gradient_mag))/(8/self.compression))# TODO scale according to bins e.g. 128 = divide by 8 / compression, i.e. divide by header bins per symbol!
# Header bins
ifis_hdr==True:
result/=4
# Convert argmax to binary
result_binary=bin(result)[2:]
# 5+3 coding for header
ifis_hdr==True:
result_binary="0"*(self.sf-2-len(result_binary))+result_binary# -2 to cut off LSB of gray code