kopia lustrzana https://github.com/projecthorus/wenet
initial
rodzic
4ff7dc182d
commit
b9f2348d8d
|
@ -0,0 +1,13 @@
|
|||
*.o
|
||||
.DS_Store
|
||||
rx/drs232_ldpc
|
||||
rx/fsk_demod
|
||||
rx/rxtemp.bin
|
||||
__pycache__
|
||||
rx/rx_images/*
|
||||
src/drs232_ldpc
|
||||
src/fsk_demod
|
||||
tx/binary_debug.bin
|
||||
*.so
|
||||
tx/*.bin
|
||||
test_images
|
|
@ -15,7 +15,6 @@
|
|||
#
|
||||
|
||||
|
||||
import serial
|
||||
import sys
|
||||
import os
|
||||
import datetime
|
||||
|
@ -598,6 +597,8 @@ if __name__ == "__main__":
|
|||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--rfm98w", default=None, type=int, help="If set, configure a RFM98W on this SPI device number.")
|
||||
parser.add_argument("--rfm98w-i2s", default=None, type=int, help="If set, configure a RFM98W on this SPI device number. Using I2S")
|
||||
parser.add_argument("--audio-device", default="hw:CARD=i2smaster,DEV=0", type=str, help="Alsa device string. Sets the audio device for rfm98w-i2s mode. (Default: hw:CARD=i2smaster,DEV=0)")
|
||||
parser.add_argument("--frequency", default=443.500, type=float, help="Transmit Frequency (MHz). (Default: 443.500 MHz)")
|
||||
parser.add_argument("--baudrate", default=115200, type=int, help="Wenet TX baud rate. (Default: 115200).")
|
||||
parser.add_argument("--serial_port", default="/dev/ttyAMA0", type=str, help="Serial Port for modulation.")
|
||||
|
@ -623,6 +624,13 @@ if __name__ == "__main__":
|
|||
serial_port = args.serial_port,
|
||||
tx_power_dbm = args.tx_power
|
||||
)
|
||||
elif args.rfm98w_i2s is not None:
|
||||
radio = RFM98W_I2S(
|
||||
spidevice = args.rfm98w_i2s,
|
||||
frequency = args.frequency,
|
||||
audio_device= args.audio_device,
|
||||
tx_power_dbm = args.tx_power
|
||||
)
|
||||
# Other radio options would go here.
|
||||
else:
|
||||
logging.critical("No radio type specified! Exiting")
|
||||
|
|
|
@ -38,7 +38,7 @@ class HardwareInterface(object):
|
|||
spi = None
|
||||
spi_speed = 1000000
|
||||
|
||||
def __init__(self, device=0):
|
||||
def __init__(self, device=0, LED=None):
|
||||
""" Configure the Raspberry GPIOs
|
||||
:rtype : None
|
||||
"""
|
||||
|
@ -46,11 +46,11 @@ class HardwareInterface(object):
|
|||
GPIO.setmode(GPIO.BCM)
|
||||
|
||||
if device == 0:
|
||||
self.LED = 5
|
||||
self.LED = 5 if LED == None else LED
|
||||
self.DIO0 = 25
|
||||
self.DIO5 = 24
|
||||
else:
|
||||
self.LED = 21
|
||||
self.LED = 21 if LED == None else LED # i2s method requires pin 21
|
||||
self.DIO0 = 16
|
||||
self.DIO5 = 12
|
||||
|
||||
|
|
|
@ -23,9 +23,19 @@ import logging
|
|||
import serial
|
||||
import time
|
||||
import numpy as np
|
||||
from SX127x.LoRa import *
|
||||
from SX127x.hardware_piloragateway import HardwareInterface
|
||||
|
||||
try:
|
||||
import alsaaudio
|
||||
except:
|
||||
logging.warning("No alsaaudio - i2s support disabled")
|
||||
|
||||
# Allow for testing without a radio
|
||||
try:
|
||||
from SX127x.LoRa import *
|
||||
from SX127x.hardware_piloragateway import HardwareInterface
|
||||
except:
|
||||
HardwareInterface = None
|
||||
logging.error("Could not load SX127x. modules")
|
||||
|
||||
class RFM98W_Serial(object):
|
||||
"""
|
||||
|
@ -224,6 +234,267 @@ class RFM98W_Serial(object):
|
|||
|
||||
return self.temperature
|
||||
|
||||
class RFM98W_I2S(object):
|
||||
"""
|
||||
RFM98W Wrapper for Wenet Transmission, using 2-FSK Direct-Asynchronous Modulation via a I2S.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
spidevice=0,
|
||||
frequency=443.500,
|
||||
audio_device="hw:CARD=i2smaster,DEV=0",
|
||||
tx_power_dbm=17,
|
||||
reinit_count=5000
|
||||
):
|
||||
|
||||
self.spidevice = spidevice
|
||||
self.frequency = frequency
|
||||
|
||||
self.rate = 96000 # only having one rate to simplify things for the moment
|
||||
# our constraints
|
||||
# 2 channels - seems to be stuck with this due to device tree
|
||||
# 2 bytes (16 bit le) - also limited
|
||||
# rates 8000,16000,22050,44100,48000 (why no 24000!?!?)
|
||||
# to keep things simple we can pick a multiple that packs into a byte
|
||||
|
||||
|
||||
self.channels = 2
|
||||
self.audio_width = 2 # bytes
|
||||
self.audio_rate = 48000
|
||||
self.bytes_per_bit = ((self.audio_rate * self.channels * self.audio_width) // self.rate)
|
||||
logging.debug(self.bytes_per_bit)
|
||||
|
||||
if (
|
||||
((self.audio_rate * self.channels * self.audio_width * 8) / self.rate)%8 !=0
|
||||
):
|
||||
raise ValueError(f"Not aligned audio rate. Must be a whole byte per bit. audio_rate: {self.audio_rate} rate: {self.rate}")
|
||||
|
||||
self.tx_power_dbm = tx_power_dbm
|
||||
self.reinit_count = reinit_count
|
||||
|
||||
self.audio_device = audio_device
|
||||
|
||||
self.hw = None
|
||||
self.lora = None
|
||||
|
||||
self.tx_packet_count = 0
|
||||
|
||||
self.temperature = -999
|
||||
|
||||
self.precompute_bytes()
|
||||
|
||||
self.periodsize = None
|
||||
|
||||
self.pcm = None
|
||||
|
||||
self.start()
|
||||
|
||||
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Initialise (or re-initialise) both the RFM98W and Serial connections.
|
||||
Configure the RFM98W into direct asynchronous FSK mode, with the appropriate power, deviation, and transmit frequency.
|
||||
"""
|
||||
|
||||
# Cleanup any open file handlers.
|
||||
if self.hw:
|
||||
self.hw.teardown()
|
||||
|
||||
if HardwareInterface:
|
||||
self.hw = HardwareInterface(self.spidevice, LED=5) # overriding LED pin due to conflict with I2S
|
||||
self.lora = LoRaRFM98W(self.hw, verbose=False)
|
||||
logging.debug(f"RFM98W - SX127x Register Dump: {self.lora.backup_registers}")
|
||||
logging.debug(f"RFM98W - SX127x device version: {hex(self.lora.get_version())}")
|
||||
else:
|
||||
self.hw = None
|
||||
self.lora = None
|
||||
|
||||
if self.hw and not self.comms_ok():
|
||||
logging.critical("RFM98W - No communication with RFM98W IC!")
|
||||
self.shutdown()
|
||||
return
|
||||
|
||||
# Deviation selection.
|
||||
if self.audio_rate == 9600:
|
||||
deviation = 4800
|
||||
elif self.audio_rate == 4800:
|
||||
deviation = 2400
|
||||
else:
|
||||
# TODO We may want a different deviation for 96000
|
||||
# Default deviation, for 115200 baud
|
||||
deviation = 71797
|
||||
|
||||
# Refer https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf
|
||||
if self.hw:
|
||||
self.lora.set_register(0x01,0x00) # FSK Sleep Mode
|
||||
self.lora.set_register(0x31,0x00) # Set Continuous Transmit Mode
|
||||
|
||||
# Get the IC temperature
|
||||
if self.hw:
|
||||
self.get_temperature()
|
||||
|
||||
if self.hw:
|
||||
self.lora.set_freq(self.frequency)
|
||||
logging.info(f"RFM98W - Frequency set to: {self.frequency} MHz.")
|
||||
|
||||
# Set Deviation (~70 kHz). Signals ends up looking a bit wider than the RFM22B version.
|
||||
_dev_lsbs = int(deviation / 61.03)
|
||||
_dev_msb = _dev_lsbs >> 8
|
||||
_dev_lsb = _dev_lsbs % 256
|
||||
if self.hw:
|
||||
self.lora.set_register(0x04,_dev_msb)
|
||||
self.lora.set_register(0x05,_dev_lsb)
|
||||
|
||||
# Set Transmit power
|
||||
if self.hw:
|
||||
tx_power_lookup = {0:0x80, 1:0x80, 2:0x80, 3:0x81, 4:0x82, 5:0x83, 6:0x84, 7:0x85, 8:0x86, 9:0x87, 10:0x88, 11:0x89, 12:0x8A, 13:0x8B, 14:0x8C, 15:0x8D, 16:0x8E, 17:0x8F}
|
||||
if self.tx_power_dbm in tx_power_lookup:
|
||||
self.lora.set_register(0x09, tx_power_lookup[self.tx_power_dbm])
|
||||
logging.info(f"RFM98W - TX Power set to {self.tx_power_dbm} dBm ({hex(tx_power_lookup[self.tx_power_dbm])}).")
|
||||
else:
|
||||
# Default to low power, 1.5mW or so
|
||||
self.lora.set_register(0x09, 0x80)
|
||||
logging.info(f"RFM98W - Unknown TX power, setting to 2 dBm (0x80).")
|
||||
|
||||
# Go into TX mode.
|
||||
self.lora.set_register(0x01,0x02) # .. via FSTX mode (where the transmit frequency actually gets set)
|
||||
self.lora.set_register(0x01,0x03) # Now we're in TX mode...
|
||||
|
||||
# Seems we need to briefly sleep before we can read the register correctly.
|
||||
time.sleep(0.1)
|
||||
|
||||
# Confirm we've gone into transmit mode.
|
||||
if self.hw and self.lora.get_register(0x01) == 0x03:
|
||||
logging.info("RFM98W - Radio initialised!")
|
||||
else:
|
||||
logging.critical("RFM98W - TX Mode not set correctly!")
|
||||
|
||||
# Now initialise the Serial port for modulation
|
||||
if self.audio_device and alsaaudio and not self.pcm:
|
||||
try:
|
||||
self.pcm = alsaaudio.PCM(device=self.audio_device)
|
||||
logging.info(f"RFM98W - Opened audio device {self.pcm.cardname()} for modulation.")
|
||||
if self.pcm.setrate(self.audio_rate) != self.audio_rate:
|
||||
logging.critical("Could not set correct audio rate for datarate")
|
||||
if self.pcm.setchannels(self.channels) != self.channels:
|
||||
logging.critical("could not set channel number")
|
||||
except Exception as e:
|
||||
logging.critical(f"Could not open audio device! Error: {str(e)}")
|
||||
elif not self.pcm:
|
||||
logging.error("No alsaaudio - debugging mode")
|
||||
self.pcm = BinaryDebug()
|
||||
|
||||
|
||||
def precompute_bytes(self):
|
||||
logging.debug("Precomputing byte lookup table")
|
||||
self.byte_to_i2s_bytes ={}
|
||||
for x in range(256):
|
||||
buffer = b''
|
||||
for bit_i in range(7,-1,-1):
|
||||
bit = (x >> (bit_i)) & 0b1
|
||||
bit = b'\xff' if bit else b'\x00'
|
||||
buffer = buffer + (bit*self.bytes_per_bit)
|
||||
self.byte_to_i2s_bytes[x] = buffer
|
||||
logging.debug("Finished creating lookup table")
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Shutdown the RFM98W, and close the SPI and Serial connections.
|
||||
"""
|
||||
|
||||
try:
|
||||
# Set radio into FSK sleep mode
|
||||
self.lora.set_register(0x01,0x00)
|
||||
logging.info("RFM98W - Set radio into sleep mode.")
|
||||
self.lora = None
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Shutdown SPI device
|
||||
self.hw.teardown()
|
||||
logging.info("RFM98W - Disconnected from SPI.")
|
||||
self.hw = None
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Close the audio device
|
||||
self.pcm.close()
|
||||
logging.info("RFM98W - Closed audio device")
|
||||
self.pcm = None
|
||||
except:
|
||||
pass
|
||||
|
||||
return
|
||||
|
||||
|
||||
def comms_ok(self):
|
||||
"""
|
||||
Test SPI communications with the RFM98W and return true if ok.
|
||||
"""
|
||||
|
||||
try:
|
||||
_ver = self.lora.get_version()
|
||||
|
||||
if _ver == 0x00 or _ver == 0xFF or _ver == None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.critical("RFM98W - Could not read device version!")
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def transmit_packet(self, packet):
|
||||
"""
|
||||
Modulate audio data, using a I2S.
|
||||
"""
|
||||
|
||||
if self.pcm:
|
||||
desired_period_size = (len(packet)*8*self.bytes_per_bit)//self.channels//self.audio_width
|
||||
if (
|
||||
self.periodsize == None or
|
||||
self.periodsize != desired_period_size
|
||||
):
|
||||
logging.debug(f"Setting period size to: {desired_period_size}")
|
||||
if self.pcm.setperiodsize(desired_period_size) != desired_period_size:
|
||||
logging.critical(f"could not set period size to match packet size: got {self.pcm.setperiodsize(desired_period_size)}")
|
||||
else:
|
||||
self.periodsize = desired_period_size
|
||||
buffer = b''
|
||||
for i_byte in packet:
|
||||
buffer = buffer + self.byte_to_i2s_bytes[i_byte]
|
||||
frame_length = (len(buffer)//self.channels//self.audio_width)
|
||||
if frame_length % self.periodsize != 0:
|
||||
logging.critical(f"buffer frames length {frame_length} != periodsize {self.periodsize}")
|
||||
self.pcm.write(buffer)
|
||||
|
||||
# Increment transmit packet counter
|
||||
self.tx_packet_count += 1
|
||||
|
||||
# If we have a reinitialisation count set, reinitialise the radio.
|
||||
if self.reinit_count:
|
||||
if self.tx_packet_count % self.reinit_count == 0:
|
||||
logging.info(f"RFM98W - Reinitialising Radio at {self.tx_packet_count} packets.")
|
||||
self.start()
|
||||
|
||||
def get_temperature(self):
|
||||
"""
|
||||
Get radio module temperature (uncalibrated)
|
||||
"""
|
||||
# Make temperature measurement
|
||||
self.temperature = self.lora.get_register(0x3c) * (-1)
|
||||
if self.temperature < -63:
|
||||
self.temperature += 255
|
||||
logging.info(f"RFM98W - Temperature: {self.temperature} C")
|
||||
|
||||
return self.temperature
|
||||
|
||||
class SerialOnly(object):
|
||||
"""
|
||||
Transmitter Wrapper that does not initialise any radios.
|
||||
|
@ -318,7 +589,7 @@ class BinaryDebug(object):
|
|||
# TODO: Add in RS232 framing
|
||||
raw_data = np.array([],dtype=np.uint8)
|
||||
for d in data:
|
||||
d_array = np.unpackbits(np.fromstring(d,dtype=np.uint8))
|
||||
d_array = np.unpackbits(np.frombuffer(bytes([d]),dtype=np.uint8))
|
||||
raw_data = np.concatenate((raw_data,[0],d_array[::-1],[1]))
|
||||
|
||||
self.f.write(raw_data.astype(np.uint8).tostring())
|
||||
|
@ -333,7 +604,9 @@ if __name__ == '__main__':
|
|||
|
||||
import time
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--rfm98w", default=None, type=int, help="If set, configure a RFM98W on this SPI device number.")
|
||||
parser.add_argument("--rfm98w", default=None, type=int, help="If set, configure a RFM98W on this SPI device number. Using UART")
|
||||
parser.add_argument("--rfm98w-i2s", default=None, type=int, help="If set, configure a RFM98W on this SPI device number. Using I2S")
|
||||
parser.add_argument("--audio-device", default="hw:CARD=i2smaster,DEV=0", type=str, help="Sets the audio device for rfm98w-i2s mode.")
|
||||
parser.add_argument("--frequency", default=443.500, type=float, help="Transmit Frequency (MHz). (Default: 443.500 MHz)")
|
||||
parser.add_argument("--baudrate", default=115200, type=int, help="Wenet TX baud rate. (Default: 115200).")
|
||||
parser.add_argument("--serial_port", default="/dev/ttyAMA0", type=str, help="Serial Port for modulation.")
|
||||
|
@ -361,6 +634,13 @@ if __name__ == '__main__':
|
|||
serial_port = args.serial_port,
|
||||
tx_power_dbm = args.tx_power
|
||||
)
|
||||
elif args.rfm98w_i2s is not None:
|
||||
radio = RFM98W_I2S(
|
||||
spidevice = args.rfm98w,
|
||||
frequency = args.frequency,
|
||||
audio_device= args.audio_device,
|
||||
tx_power_dbm = args.tx_power
|
||||
)
|
||||
# Other radio options would go here.
|
||||
else:
|
||||
logging.critical("No radio type specified! Exiting")
|
||||
|
|
|
@ -25,6 +25,8 @@ parser.add_argument("--gps", default="none", help="uBlox GPS Serial port. Defaul
|
|||
parser.add_argument("--gpsbaud", default=115200, type=int, help="uBlox GPS Baud rate. (Default: 115200)")
|
||||
parser.add_argument("--logo", default="none", help="Optional logo to overlay on image.")
|
||||
parser.add_argument("--rfm98w", default=0, type=int, help="If set, configure a RFM98W on this SPI device number.")
|
||||
parser.add_argument("--rfm98w-i2s", default=None, type=int, help="If set, configure a RFM98W on this SPI device number. Using I2S")
|
||||
parser.add_argument("--audio-device", default="hw:CARD=i2smaster,DEV=0", type=str, help="Alsa device string. Sets the audio device for rfm98w-i2s mode. (Default: hw:CARD=i2smaster,DEV=0)")
|
||||
parser.add_argument("--frequency", default=443.500, type=float, help="Transmit Frequency (MHz). (Default: 443.500 MHz)")
|
||||
parser.add_argument("--baudrate", default=115200, type=int, help="Wenet TX baud rate. (Default: 115200).")
|
||||
parser.add_argument("--serial_port", default="/dev/ttyAMA0", type=str, help="Serial Port for modulation.")
|
||||
|
@ -67,6 +69,13 @@ if args.rfm98w is not None:
|
|||
serial_port = args.serial_port,
|
||||
tx_power_dbm = args.tx_power
|
||||
)
|
||||
elif args.rfm98w_i2s is not None:
|
||||
radio = RFM98W_I2S(
|
||||
spidevice = args.rfm98w_i2s,
|
||||
frequency = args.frequency,
|
||||
audio_device= args.audio_device,
|
||||
tx_power_dbm = args.tx_power
|
||||
)
|
||||
# Other radio options would go here.
|
||||
else:
|
||||
logging.critical("No radio type specified! Exiting")
|
||||
|
|
|
@ -40,6 +40,8 @@ def transmit_file(filename, tx_object):
|
|||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--rfm98w", default=None, type=int, help="If set, configure a RFM98W on this SPI device number.")
|
||||
parser.add_argument("--rfm98w-i2s", default=None, type=int, help="If set, configure a RFM98W on this SPI device number. Using I2S")
|
||||
parser.add_argument("--audio-device", default="hw:CARD=i2smaster,DEV=0", type=str, help="Alsa device string. Sets the audio device for rfm98w-i2s mode. (Default: hw:CARD=i2smaster,DEV=0)")
|
||||
parser.add_argument("--frequency", default=443.500, type=float, help="Transmit Frequency (MHz). (Default: 443.500 MHz)")
|
||||
parser.add_argument("--baudrate", default=115200, type=int, help="Wenet TX baud rate. (Default: 115200).")
|
||||
parser.add_argument("--serial_port", default="/dev/ttyAMA0", type=str, help="Serial Port for modulation.")
|
||||
|
@ -65,6 +67,13 @@ if args.rfm98w is not None:
|
|||
serial_port = args.serial_port,
|
||||
tx_power_dbm = args.tx_power
|
||||
)
|
||||
elif args.rfm98w_i2s is not None:
|
||||
radio = RFM98W_I2S(
|
||||
spidevice = args.rfm98w_i2s,
|
||||
frequency = args.frequency,
|
||||
audio_device= args.audio_device,
|
||||
tx_power_dbm = args.tx_power
|
||||
)
|
||||
# Other radio options would go here.
|
||||
else:
|
||||
logging.critical("No radio type specified! Exiting")
|
||||
|
|
Ładowanie…
Reference in New Issue