xssfox 2025-07-07 16:19:36 +10:00
rodzic 3a84f687f9
commit 15097c958d
4 zmienionych plików z 66 dodań i 42 usunięć

Wyświetl plik

@ -85,11 +85,25 @@ class PacketTX(object):
fec=True,
udp_listener = None,
log_file = None):
self.min_radio_rotation_time_seconds = 60 # only when queues empty
self.last_rotate = time.monotonic()
self.radio = radio
if type(radio) == list: # handle multiple radios
self.radios = radio
self.radio = radio.pop(0)
self.radios.append(self.radio) # rotate since we've used the first one
else:
self.radios = None
self.radio = radio
self.payload_length = payload_length
self.callsign = callsign.encode('ascii')
if type(callsign) == list:
self.callsigns = [x.encode('ascii') for x in callsign]
self.callsign = self.callsigns.pop(0)
self.callsigns.append(self.callsign)
else:
self.callsign = callsign.encode('ascii')
self.fec = fec
self.crc16 = crcmod.predefined.mkCrcFun('crc-ccitt-false')
@ -157,10 +171,23 @@ class PacketTX(object):
packet = self.telemetry_queue.get_nowait()
self.radio.transmit_packet(packet)
elif self.ssdv_queue.qsize()>0:
packet = self.ssdv_queue.get_nowait()
self.radio.transmit_packet(packet)
(packet, callsign) = self.ssdv_queue.get_nowait()
if callsign == self.callsign:
self.radio.transmit_packet(packet)
else:
logging.info("packet not intended for this callsign - dropping")
else:
self.radio.transmit_packet(self.idle_message)
if self.radios:
if time.monotonic() > self.min_radio_rotation_time_seconds + self.last_rotate:
logging.info("Rotating radio")
logging.info(self.radios)
self.last_rotate = time.monotonic()
self.callsign = self.callsigns.pop(0)
self.callsigns.append(self.callsign)
self.radio = self.radios.pop(0)
self.radios.append(self.radio)
self.radio.start()
# time.sleep(0.1)
# commented this out as we should probably always be sending something
# this can cause gaps in i2s, which while won't hurt the performance can be annoying
@ -192,11 +219,11 @@ class PacketTX(object):
# New packet queueing and queue querying functions (say that 3 times fast)
def queue_image_packet(self,packet):
self.ssdv_queue.put(self.frame_packet(packet, self.fec))
def queue_image_packet(self,packet,callsign):
self.ssdv_queue.put([self.frame_packet(packet, self.fec),callsign])
def queue_image_file(self, filename):
def queue_image_file(self, filename, callsign):
""" Read in <filename> and transmit it, 256 bytes at a time.
Intended for transmitting SSDV images.
"""
@ -205,7 +232,7 @@ class PacketTX(object):
f = open(filename,'rb')
for x in range(file_size//256):
data = f.read(256)
self.queue_image_packet(data)
self.queue_image_packet(data,callsign)
f.close()
return True
except:

Wyświetl plik

@ -389,7 +389,7 @@ class WenetPiCamera2(object):
return True
def ssdvify(self, filename="output.jpg", image_id=0, quality=6):
def ssdvify(self, filename="output.jpg", image_id=0, quality=6, tx=None):
""" Convert a supplied JPEG image to SSDV.
Returns the filename of the converted SSDV image.
@ -417,6 +417,8 @@ class WenetPiCamera2(object):
file_basename = filename[:-4]
# Construct SSDV command-line.
if tx:
self.callsign = tx.callsign.decode()
ssdv_command = "ssdv -e -n -q %d -c %s -i %d picam_temp.jpg picam_temp.ssdv" % (quality, self.callsign, image_id)
print(ssdv_command)
# Update debug message.
@ -503,7 +505,13 @@ class WenetPiCamera2(object):
self.debug_message("Image Post-Processing Failed: %s" % error_str)
# SSDV'ify the image.
ssdv_filename = self.ssdvify(capture_filename, image_id=image_id)
# Wait until the transmit queue is empty before pushing in packets.
self.debug_message("Waiting for SSDV TX queue to empty.")
while tx.image_queue_empty() == False:
sleep(1) # Sleep for a short amount of time to allow switching of radio
if self.auto_capture_running == False:
return
ssdv_filename = self.ssdvify(capture_filename, image_id=image_id, tx=tx)
# Check the SSDV Conversion has completed properly. If not, continue
if ssdv_filename == "FAIL":
@ -514,18 +522,11 @@ class WenetPiCamera2(object):
# Otherwise, read in the file and push into the TX buffer.
file_size = os.path.getsize(ssdv_filename)
# Wait until the transmit queue is empty before pushing in packets.
self.debug_message("Waiting for SSDV TX queue to empty.")
while tx.image_queue_empty() == False:
sleep(0.05) # Sleep for a short amount of time.
if self.auto_capture_running == False:
return
# Inform ground station we are about to send an image.
self.debug_message("Transmitting %d PiCam SSDV Packets." % (file_size//256))
# Push SSDV file into transmit queue.
tx.queue_image_file(ssdv_filename)
tx.queue_image_file(ssdv_filename, self.callsign.encode('ascii'))
# Increment image ID.
image_id = (image_id + 1) % 256

Wyświetl plik

@ -228,7 +228,7 @@ class RFM98W_Serial(RFM98W):
self.serial_port = serial_port
super().__init__(spidevice,frequency,baudrate,tx_power_dbm,reinit_count)
super().__init__(spidevice,frequency,baudrate,tx_power_dbm,reinit_count,led=5)
self.start()
@ -242,6 +242,7 @@ class RFM98W_Serial(RFM98W):
if self.serial_port:
try:
self.serial = serial.Serial(self.serial_port, self.baudrate)
self.serial.break_condition=True # Set UART to low when we aren't using it. Used for dual tx mode
logging.info(f"RFM98W - Opened Serial port {self.serial_port} for modulation.")
except Exception as e:
logging.critical(f"Could not open serial port! Error: {str(e)}")
@ -274,8 +275,10 @@ class RFM98W_Serial(RFM98W):
"""
Modulate serial data, using a UART.
"""
self.serial.break_condition=False
if self.serial:
self.serial.write(packet)
self.serial.break_condition=True
super().transmit_packet(packet) # used to reinit the radio occasionally

Wyświetl plik

@ -20,15 +20,15 @@ from radio_wrappers import *
parser = argparse.ArgumentParser()
parser.add_argument("callsign", default="N0CALL", help="Payload Callsign")
parser.add_argument("callsign", nargs="+", action="extend", help="Payload Callsign")
parser.add_argument("--gps", default="none", help="uBlox GPS Serial port. Defaults to /dev/ttyACM0")
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=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=None, type=int, help="Wenet TX baud rate. (Default: 115200 for uart and 96000 for I2S). Known working I2S baudrates: 8000, 24000, 48000, 96000.")
parser.add_argument("--frequency", nargs="+", type=float, help="Transmit Frequency (MHz). (Default: 443.500 MHz)",action="extend")
parser.add_argument("--baudrate", nargs="+", type=int,action="extend", help="Wenet TX baud rate. (Default: 115200 for uart and 96000 for I2S). Known working I2S baudrates: 8000, 24000, 48000, 96000.")
parser.add_argument("--serial_port", default="/dev/ttyAMA0", type=str, help="Serial Port for modulation.")
parser.add_argument("--tx_power", default=17, type=int, help="Transmit power in dBm (Default: 17 dBm, 50mW. Allowed values: 2-17)")
parser.add_argument("--vflip", action='store_true', default=False, help="Flip captured image vertically.")
@ -45,12 +45,6 @@ parser.add_argument("--image_delay", type=float, default=1.0, help="Delay time b
parser.add_argument("-v", "--verbose", action='store_true', default=False, help="Show additional debug info.")
args = parser.parse_args()
if args.baudrate == None:
if args.rfm98w:
args.baudrate = 115200
elif args.rfm98w_i2s:
args.baudrate = 96000
if args.verbose:
logging_level = logging.DEBUG
else:
@ -62,36 +56,35 @@ logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s", level=loggi
callsign = args.callsign
# Truncate callsign if it's too long.
if len(callsign) > 6:
callsign = callsign[:6]
callsign = [x[:6] for x in args.callsign]
print("Using Callsign: %s" % callsign)
radios = []
if args.rfm98w is not None:
radio = RFM98W_Serial(
radios.append(RFM98W_Serial(
spidevice = args.rfm98w,
frequency = args.frequency,
baudrate = args.baudrate,
frequency = args.frequency.pop(0),
baudrate = args.baudrate.pop(0),
serial_port = args.serial_port,
tx_power_dbm = args.tx_power
)
elif args.rfm98w_i2s is not None:
radio = RFM98W_I2S(
))
if args.rfm98w_i2s is not None:
radios.append(RFM98W_I2S(
spidevice = args.rfm98w_i2s,
baudrate = args.baudrate,
frequency = args.frequency,
baudrate = args.baudrate.pop(0),
frequency = args.frequency.pop(0),
audio_device= args.audio_device,
tx_power_dbm = args.tx_power
)
))
# Other radio options would go here.
else:
if not radios:
logging.critical("No radio type specified! Exiting")
sys.exit(1)
# Start up Wenet TX.
picam = None
tx = PacketTX.PacketTX(radio=radio, callsign=callsign, log_file="debug.log", udp_listener=55674)
tx = PacketTX.PacketTX(radio=radios, callsign=callsign, log_file="debug.log", udp_listener=55674)
tx.start_tx()
# Sleep for a second to let the transmitter fire up.