From 3828ff03b52aa57a2eb29f4932512e6a9edb079d Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Sat, 14 Sep 2024 21:12:27 +0930 Subject: [PATCH] Many changes to tx code to better handle radio upsets. Move to libcamera2 as the default --- start_tx.sh | 91 ++++++++++--- start_tx_systemd.sh | 65 ---------- tx/PacketTX.py | 68 ++++++---- tx/SX127x/LoRa.py | 2 +- tx/WenetPiCamera2.py | 46 +++++-- tx/init_rfm22b.py | 279 ---------------------------------------- tx/init_rfm98w.py | 91 ------------- tx/radio_wrappers.py | 285 +++++++++++++++++++++++++++++++++++++++++ tx/tx_picamera2_gps.py | 83 +++++++++--- tx/tx_test_images.py | 40 +++++- wenet_tx.service | 4 +- 11 files changed, 542 insertions(+), 512 deletions(-) delete mode 100644 start_tx_systemd.sh delete mode 100644 tx/init_rfm22b.py delete mode 100644 tx/init_rfm98w.py create mode 100644 tx/radio_wrappers.py diff --git a/start_tx.sh b/start_tx.sh index 8255398..cecdfa0 100755 --- a/start_tx.sh +++ b/start_tx.sh @@ -1,9 +1,9 @@ #!/bin/bash # -# Wenet TX-side Initialisation Script -# 2022-03-19 Mark Jessop +# Wenet TX-side Initialisation Script - Systemd Unit Version +# 2024-07-21 Mark Jessop # -# Run this to set up an attached RFM22B/RFM98W and start transmitting! +# Run this to set up an attached RFM98W and start transmitting! # Replace the transmit frequency and callsign with your own. # @@ -11,31 +11,92 @@ # This MUST be <= 6 characters long. MYCALL=N0CALL -# The centre frequency of the Wenet transmission. +# The centre frequency of the Wenet transmission, in MHz. TXFREQ=443.500 +# Transmit power, in dBm +# Allowed values are from 2 through 17 dBm. +TXPOWER=17 + +# GPS Port +# Note that we only support uBlox GPS units +# set this to none to disable GPS support +GPSPORT=/dev/ttyACM0 + +# Image settings +# Image scaling - Scale the 'native' image resolution of the attached camera by this much +# before transmitting. +TX_IMAGE_SCALING=0.5 + +# White Balance settings +# Allowed Values: Auto, Daylight, Cloudy, Incandescent, Tungesten, Fluorescent, Indoor +WHITEBALANCE=Auto + +# Refer near the end of this file for image flipping and overlay options + # Baud Rate # Known working transmit baud rates are 115200 (the preferred default). # Lower baud rates *may* work, but will need a lot of testing on the receiver # chain to be sure they perform correctly. BAUDRATE=115200 -# GPS Port -# Note that we only support uBlox GPS units -GPSPORT=/dev/ttyACM0 +# RFM98W SPI Device +# SPI device number of your RFM98W chip +# This will either be 0 or 1 on a RPi. +SPIDEVICE=0 + +# Modulation UART +# The UART used to modulate the RFM98W with our Wenet transmission +# We want to be using the PL011 UART, *not* the Mini-UART +# On a Pi Zero W, you may need to disable bluetooth. See here for more info: +# https://www.raspberrypi.com/documentation/computers/configuration.html#uarts-and-device-tree +SERIALPORT=/dev/ttyAMA0 + # CHANGE THE FOLLOWING LINE TO REFLECT THE ACTUAL PATH TO THE TX FOLDER. # i.e. it may be /home/username/dev/wenet/tx/ cd /home/pi/wenet/tx/ -#Uncomment to initialise a RFM22B (untested with Python 3) -#python init_rfm22b.py $TXFREQ -# Uncomment for use with a RFM98W -python3 init_rfm98w.py --frequency $TXFREQ --baudrate $BAUDRATE +# Wait here until the SPI devices are available. +# This can take a few tens of seconds after boot. +timeout=20 +echo "Checking that the SPI devices exist..." +while : ; do + [[ -e "/dev/spidev0.0" ]] && break + + if [ "$timeout" == 0 ]; then + echo "Did not find SPI device in timeout period!" + exit 1 + # At this point this script exits, and systemd should restart us anyway. + fi + + echo "Waiting another 2 seconds for SPI to be available." + sleep 2 + ((timeout--)) +done + +echo "Waiting another 10 seconds before startup." +sleep 10 + # Start the main TX Script. -# Note that you can also add --logo /path/to/logo.png to this to add a logo overlay. -python3 tx_picam_gps.py --baudrate $BAUDRATE --gps $GPSPORT $MYCALL & +# +# Additional configuration lines you may wish to add or remove before the $CALLSIGN line may include: +# Flip the image vertically and horizontally (e.g. if the camera is mounted upside down) +# --vflip --hflip \ +# Add a logo overlay in the bottom right of the image. This must be a transparent PNG file. +# --logo yourlogo.png \ +# Set a fixed focus position on a PiCam v3 (NOTE: The Picamv3 focus drifts with temperature - beware!!!) +# --lensposition 0.5 \ -# If you don't want any GPS overlays, you can comment the above line and run: -# python WenetPiCam.py --baudrate $BAUDRATE $MYCALL & +python3 tx_picamera2_gps.py \ + --rfm98w $SPIDEVICE \ + --baudrate $BAUDRATE \ + --frequency $TXFREQ \ + --serial_port $SERIALPORT \ + --tx_power $TXPOWER \ + --gps $GPSPORT \ + --resize $TX_IMAGE_SCALING \ + --whitebalance $WHITEBALANCE \ + --vflip --hflip \ + $MYCALL diff --git a/start_tx_systemd.sh b/start_tx_systemd.sh deleted file mode 100644 index 73320ad..0000000 --- a/start_tx_systemd.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash -# -# Wenet TX-side Initialisation Script - Systemd Unit Version -# 2024-07-21 Mark Jessop -# -# Run this to set up an attached RFM22B/RFM98W and start transmitting! -# Replace the transmit frequency and callsign with your own. -# - -# A callsign which will be included in the Wenet Packets. -# This MUST be <= 6 characters long. -MYCALL=VK5ARG - -# The centre frequency of the Wenet transmission. -TXFREQ=443.500 - -# Baud Rate -# Known working transmit baud rates are 115200 (the preferred default). -# Lower baud rates *may* work, but will need a lot of testing on the receiver -# chain to be sure they perform correctly. -BAUDRATE=115200 - -# GPS Port -# Note that we only support uBlox GPS units -GPSPORT=/dev/ttyACM0 - -# CHANGE THE FOLLOWING LINE TO REFLECT THE ACTUAL PATH TO THE TX FOLDER. -# i.e. it may be /home/username/dev/wenet/tx/ -cd /home/pi/wenet/tx/ - -# Wait here until the SPI devices are available. -# This can take a few tens of seconds after boot. -timeout=20 -while : ; do - [[ -e "/dev/spidev0.0" ]] && break - - if [ "$timeout" == 0 ]; then - echo "Did not find SPI device in timeout period!" - exit 1 - # At this point this script exits, and systemd should restart us anyway. - fi - - echo "Waiting another 2 seconds for SPI to be available." - sleep 2 - ((timeout--)) -done - -echo "Waiting another 10 seconds before startup." -sleep 10 - -#Uncomment to initialise a RFM22B (untested with Python 3) -#python init_rfm22b.py $TXFREQ -# Uncomment for use with a RFM98W -python3 init_rfm98w.py --frequency $TXFREQ --baudrate $BAUDRATE - - -# Start the main TX Script. -# Do not add a & on the end of these lines when running via systemd! - -# Note that you can also add --logo /path/to/logo.png to this to add a logo overlay. -# If using a Picam HQ, add a --picamhq argument into this line before the --gps argument -python3 tx_picam_gps.py --baudrate $BAUDRATE --gps $GPSPORT $MYCALL - -# If you don't want any GPS overlays, you can comment the above line and run: -# python WenetPiCam.py --baudrate $BAUDRATE $MYCALL \ No newline at end of file diff --git a/tx/PacketTX.py b/tx/PacketTX.py index 981f79f..5a3deb5 100644 --- a/tx/PacketTX.py +++ b/tx/PacketTX.py @@ -28,6 +28,7 @@ from time import sleep from threading import Thread import numpy as np from ldpc_encoder import * +from radio_wrappers import * from queue import Queue class PacketTX(object): @@ -75,23 +76,16 @@ class PacketTX(object): # WARNING: 115200 baud is ACTUALLY 115386.834 baud, as measured using a freq counter. def __init__(self, - serial_port="/dev/ttyAMA0", - serial_baud=115200, + # Radio wrapper, for radio setup and modulation. + radio, + # User callsign, should be used in the idle sequence, but currently isn't... + callsign="N0CALL", payload_length=256, fec=True, - debug = False, - callsign="N0CALL", udp_listener = None, log_file = None): - # Instantiate our low-level transmit interface, be it a serial port, or the BinaryDebug class. - if debug == True: - self.s = BinaryDebug() - self.debug = True - else: - self.debug = False - self.s = serial.Serial(serial_port,serial_baud) - + self.radio = radio self.payload_length = payload_length self.callsign = callsign.encode('ascii') @@ -159,19 +153,16 @@ class PacketTX(object): while self.transmit_active: if self.telemetry_queue.qsize()>0: packet = self.telemetry_queue.get_nowait() - self.s.write(packet) + self.radio.transmit_packet(packet) elif self.ssdv_queue.qsize()>0: packet = self.ssdv_queue.get_nowait() - self.s.write(packet) + self.radio.transmit_packet(packet) else: - if not self.debug: - self.s.write(self.idle_message) - else: - # TODO: Tune this value. - sleep(0.05) + self.radio.transmit_packet(self.idle_message) + time.sleep(0.1) print("Closing Thread") - self.s.close() + self.radio.shutdown() def close(self): @@ -534,16 +525,39 @@ if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() - parser.add_argument("--txport", default="/dev/ttyAMA0", type=str, help="Transmitter serial port. Defaults to /dev/ttyAMA0") - parser.add_argument("--baudrate", default=115200, type=int, help="Transmitter baud rate. Defaults to 115200 baud.") + parser.add_argument("--rfm98w", default=None, type=int, help="If set, configure a RFM98W on this SPI device number.") + 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.") + 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("-v", "--verbose", action='store_true', default=False, help="Show additional debug info.") args = parser.parse_args() - debug_output = False # If True, packet bits are saved to debug.bin as one char per bit. + if args.verbose: + logging_level = logging.DEBUG + else: + logging_level = logging.INFO + + # Set up logging + logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s", level=logging_level) + + radio = None + + if args.rfm98w is not None: + radio = RFM98W_Serial( + spidevice = args.rfm98w, + frequency = args.frequency, + baudrate = args.baudrate, + serial_port = args.serial_port, + tx_power_dbm = args.tx_power + ) + # Other radio options would go here. + else: + logging.critical("No radio type specified! Exiting") + sys.exit(1) tx = PacketTX( - debug=debug_output, - serial_port=args.txport, - serial_baud=args.baudrate, + radio=radio, udp_listener=55674) tx.start_tx() @@ -556,4 +570,4 @@ if __name__ == "__main__": except KeyboardInterrupt: tx.close() - print("Closing") \ No newline at end of file + logging.info("Closing") \ No newline at end of file diff --git a/tx/SX127x/LoRa.py b/tx/SX127x/LoRa.py index 7d1e3ee..8f2d31b 100755 --- a/tx/SX127x/LoRa.py +++ b/tx/SX127x/LoRa.py @@ -72,7 +72,7 @@ def setter(register_address): ############################################### Definition of the LoRa class ########################################### -class LoRa(object): +class LoRaRFM98W(object): BOARD = None spi = None diff --git a/tx/WenetPiCamera2.py b/tx/WenetPiCamera2.py index 6028a8f..8375f34 100644 --- a/tx/WenetPiCamera2.py +++ b/tx/WenetPiCamera2.py @@ -35,6 +35,17 @@ class WenetPiCamera2(object): """ + # White balance text to enum lookup + wb_lookup = { + "auto": controls.AwbModeEnum.Auto, + "incandescent": controls.AwbModeEnum.Incandescent, + "fluorescent": controls.AwbModeEnum.Fluorescent, + "tungsten": controls.AwbModeEnum.Tungsten, + "indoor": controls.AwbModeEnum.Indoor, + "daylight": controls.AwbModeEnum.Daylight, + "cloudy": controls.AwbModeEnum.Cloudy + } + def __init__(self, callsign = "N0CALL", tx_resolution=(1936,1088), @@ -42,7 +53,7 @@ class WenetPiCamera2(object): image_delay=0.0, vertical_flip = False, horizontal_flip = False, - greyworld = False, + whitebalance = 'auto', lens_position = 0.0, temp_filename_prefix = 'picam_temp', debug_ptr = None @@ -64,7 +75,7 @@ class WenetPiCamera2(object): vertical_flip: Flip captured images vertically. horizontal_flip: Flip captured images horizontally. Used to correct for picam orientation. - greyworld: Use Greyworld AWB setting, for IR-filtered images. + whitebalance: White balance mode - allowed values: Auto, Incandescent, Tungesten, Fluorescent, Indoor, Daylight, Cloudy lens_position: Lens Position setting (float), 0.0 = Infinity, 10 = very close. Only usable on Pi Camera v3 modules. @@ -81,12 +92,17 @@ class WenetPiCamera2(object): self.num_images = num_images self.image_delay = image_delay self.callsign = callsign - self.tx_resolution = tx_resolution + self.tx_resolution_init = tx_resolution self.horizontal_flip = horizontal_flip self.vertical_flip = vertical_flip - self.greyworld = greyworld self.lens_position = lens_position + if whitebalance.lower() in self.wb_lookup: + self.whitebalance = self.wb_lookup[whitebalance.lower()] + else: + self.whitebalance = self.wb_lookup['auto'] + + self.init_camera() @@ -96,7 +112,18 @@ class WenetPiCamera2(object): self.camera_properties = self.cam.camera_properties - self.debug_ptr("Camera Resolution: " + str(self.camera_properties['PixelArraySize'])) + self.debug_ptr("Camera Native Resolution: " + str(self.camera_properties['PixelArraySize'])) + + # If the user has explicitly specified the transmit image resolution, use it. + if type(self.tx_resolution_init) == tuple: + self.tx_resolution = self.tx_resolution_init + self.debug_ptr(f"Transmit Resolution set to {str(self.tx_resolution)}") + # Otherwise, has the user provided a floating point scaling factor? + elif type(self.tx_resolution_init) == float: + res_x = 16*int(self.camera_properties['PixelArraySize'][0]*self.tx_resolution_init/16) + res_y = 16*int(self.camera_properties['PixelArraySize'][1]*self.tx_resolution_init/16) + self.tx_resolution = (res_x, res_y) + self.debug_ptr(f"Transmit Resolution set to {str(self.tx_resolution)}, scaled {self.tx_resolution_init} from native.") # Configure camera, including flip settings. capture_config = self.cam.create_still_configuration( @@ -105,14 +132,15 @@ class WenetPiCamera2(object): self.cam.configure(capture_config) # Set other settings, White Balance, exposure metering, etc. + self.cam.set_controls( - {'AwbMode': controls.AwbModeEnum.Daylight, + {'AwbMode': self.whitebalance, 'AeMeteringMode': controls.AeMeteringModeEnum.Matrix, 'NoiseReductionMode': controls.draft.NoiseReductionModeEnum.Off} ) # Set Pi Camera 3 lens position - if 'LensPosition' in self.cam.camera_controls: + if 'LensPosition' in self.cam.camera_controls and self.lens_position>0: self.debug_ptr("Configured lens position to " + str(self.lens_position)) self.cam.set_controls({"AfMode": controls.AfModeEnum.Manual, "LensPosition": self.lens_position}) @@ -153,13 +181,13 @@ class WenetPiCamera2(object): # Set other settings, White Balance, exposure metering, etc. self.cam.set_controls( - {'AwbMode': controls.AwbModeEnum.Daylight, + {'AwbMode': self.whitebalance, 'AeMeteringMode': controls.AeMeteringModeEnum.Matrix, 'NoiseReductionMode': controls.draft.NoiseReductionModeEnum.Off} ) # Set Pi Camera 3 lens position - if 'LensPosition' in self.cam.camera_controls: + if 'LensPosition' in self.cam.camera_controls and self.lens_position>0: self.debug_ptr("Configured lens position to " + str(self.lens_position)) self.cam.set_controls({"AfMode": controls.AfModeEnum.Manual, "LensPosition": self.lens_position}) diff --git a/tx/init_rfm22b.py b/tx/init_rfm22b.py deleted file mode 100644 index 2a806a8..0000000 --- a/tx/init_rfm22b.py +++ /dev/null @@ -1,279 +0,0 @@ -#!/usr/bin/env python -# -# RFM22B Initialisation for Horus High Speed Telemetry -# -# Copyright (C) 2018 Mark Jessop -# Released under GNU GPL v3 or later -# -# A mash together of a few different libraries, including: -# https://github.com/MaxBaex/raspi_rfm22 -# https://github.com/omcaree/rfm22b/blob/master/src/rfm22b.cpp -# -# Uses spidev for SPI comms. -# -# Note: This is fairly limited, and was only intended to get a RFM22B -# into direct-async TX mode. -# Lots of functions (i.e. anything to do with packets) are unimplemented. -# -# Electrical Connections: -# GPIO0 <-> TX_ANT -# GPIO1 <-> RX_ANT -# SDN <-> GND (Not sure if this is a good thing, not being about to hard reset it..) -# GPIO2 <-> UART TXD (for direct mode TX) -# SPI - connected to CE1 (can change this when instantiating the object.) -# - -import spidev, sys, time - -class REG: - DEVICE_TYPE = 0x00 - DEVICE_VERSION = 0x01 - DEVICE_STATUS = 0x02 - INTERRUPT_STATUS_1 = 0x03 - INTERRUPT_STATUS_2 = 0x04 - INTERRUPT_ENABLE_1 = 0x05 - INTERRUPT_ENABLE_2 = 0x06 - OPERATING_FUNCTION_CONTROL_1 = 0x07 - OPERATING_FUNCTION_CONTROL_2 = 0x08 - CRYSTAL_OSCILLATOR_LOAD = 0x09 - MICROCONTROLLER_CLOCK_OUTPUT = 0x0A - GPIO0_CONFIGURATION = 0x0B - GPIO1_CONFIGURATION = 0x0C - GPIO2_CONFIGURATION = 0x0D - I_O_PORT_CONFIGURATION = 0x0E - ADC_CONFIGURATION = 0x0F - ADC_SENSOR_AMPLIFIER_OFFSET = 0x10 - ADC_VALUE = 0x11 - TEMPERATURE_SENSOR_CONTROL = 0x12 - TEMPERATURE_VALUE_OFFSET = 0x13 - WAKE_UP_TIMER_PERIOD_1 = 0x14 - WAKE_UP_TIMER_PERIOD_2 = 0x15 - WAKE_UP_TIMER_PERIOD_3 = 0x16 - WAKE_UP_TIMER_VALUE_1 = 0x17 - WAKE_UP_TIMER_VALUE_2 = 0x18 - LOW_DUTY_CYCLE_MODE_DURATION = 0x19 - LOW_BATTERY_DETECTION_THRESHOLD = 0x1A - BATTERY_VOLTAGE_LEVEL = 0x1B - IF_FILTER_BANDWIDTH = 0x1C - AFC_LOOP_GEARSHIFT_OVERRIDE = 0x1D - AFC_TIMING_CONTROL = 0x1E - CLOCK_RECOVERY_GEARSHIFT_OVERRIDE = 0x1F - CLOCK_RECOVERY_OVERSAMPLING_RATIO = 0x20 - CLOCK_RECOVERY_OFFSET_2 = 0x21 - CLOCK_RECOVERY_OFFSET_1 = 0x22 - CLOCK_RECOVERY_OFFSET_0 = 0x23 - CLOCK_RECOVERY_TIMING_LOOP_GAIN_1 = 0x24 - CLOCK_RECOVERY_TIMING_LOOP_GAIN_0 = 0x25 - RECEIVED_SIGNAL_STRENGTH_INDICATOR = 0x26 - RSSI_THRESSHOLF_FOR_CLEAR_CHANNEL_INDICATOR = 0x27 - ANTENNA_DIVERSITY_REGISTER_1 = 0x28 - ANTENNA_DIVERSITY_REGISTER_2 = 0x29 - AFC_LIMITER = 0x2A - AFC_CORRECTION_READ = 0x2B - OOK_COUNTER_VALUE_1 = 0x2C - OOK_COUNTER_VALUE_2 = 0x2D - SLICER_PEAK_HOLD = 0x2E - # Register 0x2F reserved - DATA_ACCESS_CONTROL = 0x30 - EzMAC_STATUS = 0x31 - HEADER_CONTROL_1 = 0x32 - HEADER_CONTROL_2 = 0x33 - PREAMBLE_LENGTH = 0x34 - PREAMBLE_DETECTION_CONTROL = 0x35 - SYNC_WORD_3 = 0x36 - SYNC_WORD_2 = 0x37 - SYNC_WORD_1 = 0x38 - SYNC_WORD_0 = 0x39 - TRANSMIT_HEADER_3 = 0x3A - TRANSMIT_HEADER_2 = 0x3B - TRANSMIT_HEADER_1 = 0x3C - TRANSMIT_HEADER_0 = 0x3D - TRANSMIT_PACKET_LENGTH = 0x3E - CHECK_HEADER_3 = 0x3F - CHECK_HEADER_2 = 0x40 - CHECK_HEADER_1 = 0x41 - CHECK_HEADER_0 = 0x42 - HEADER_ENABLE_3 = 0x43 - HEADER_ENABLE_2 = 0x44 - HEADER_ENABLE_1 = 0x45 - HEADER_ENABLE_0 = 0x46 - RECEIVED_HEADER_3 = 0x47 - RECEIVED_HEADER_2 = 0x48 - RECEIVED_HEADER_1 = 0x49 - RECEIVED_HEADER_0 = 0x4A - RECEIVED_PACKET_LENGTH = 0x4B - # Registers 0x4C-4E reserved - ADC8_CONTROL = 0x4F - # Registers 0x50-5F reserved - CHANNEL_FILTER_COEFFICIENT_ADDRESS = 0x60 - # Register 0x61 reserved - CRYSTAL_OSCILLATOR_CONTROL_TEST = 0x62 - # Registers 0x63-68 reserved - AGC_OVERRIDE_1 = 0x69 - # Registers 0x6A-0x6C reserved - TX_POWER = 0x6D - TX_DATA_RATE_1 = 0x6E - TX_DATA_RATE_0 = 0x6F - MODULATION_MODE_CONTROL_1 = 0x70 - MODULATION_MODE_CONTROL_2 = 0x71 - FREQUENCY_DEVIATION = 0x72 - FREQUENCY_OFFSET_1 = 0x73 - FREQUENCY_OFFSET_2 = 0x74 - FREQUENCY_BAND_SELECT = 0x75 - NOMINAL_CARRIER_FREQUENCY_1 = 0x76 - NOMINAL_CARRIER_FREQUENCY_0 = 0x77 - # Register 0x78 reserved - FREQUENCY_HOPPING_CHANNEL_SELECT = 0x79 - FREQUENCY_HOPPING_STEP_SIZE = 0x7A - # Register 0x7B reserved - TX_FIFO_CONTROL_1 = 0x7C - TX_FIFO_CONTROL_2 = 0x7D - RX_FIFO_CONTROL = 0x7E - FIFO_ACCESS = 0x7F - -class MODE: - IDLE = 0 - RX = 1 - TX = 2 - -class TXPOW: - TXPOW_1DBM = 0x00 - TXPOW_2DBM = 0x01 - TXPOW_5DBM = 0x02 - TXPOW_8DBM = 0x03 - TXPOW_11DBM = 0x04 - TXPOW_14DBM = 0x05 - TXPOW_17DBM = 0x06 - TXPOW_20DBM = 0x07 - -class CONFIGS: -# Register: 1c, 1f, 20, 21, 22, 23, 24, 25, 2c, 2d, 2e, 58, 69, 6e, 6f, 70, 71, 72 - REGISTERS = [0x1C,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x2C,0x2D,0x2E,0x58,0x69,0x6E,0x6F,0x70,0x71,0x72] - DIRECT_120K = [0x8a, 0x03, 0x60, 0x01, 0x55, 0x55, 0x02, 0xad, 0x40, 0x0a, 0x50, 0x80, 0x60, 0x20, 0x00, 0x00, 0x02, 0x60] - -class RFM22B(object): - - def __init__(self,device=1): - self.spi = spidev.SpiDev() - self.spi.open(0,device) - self.spi.max_speed_hz = 1000000 - - if not self.check_connection(): - print("Init Failed!") - self.spi.close() - sys.exit(1) - - self.setup_basic() - - print("Init Complete!") - - def close(self): - self.spi.close() - - def read_register(self,address): - return self.spi.xfer([address,0])[1] - - def write_register(self,address,data): - return self.spi.xfer([address | 0x80, data]) - - def check_connection(self): - device_type = self.read_register(REG.DEVICE_TYPE) - device_version = self.read_register(REG.DEVICE_VERSION) - - print("Device Type:\t %d" % device_type) - print("Device Version:\t %d" % device_version) - - if (device_type != 8) or (device_version != 6): - print("Wrong Device?!") - return False - else: - return True -# 441.2E6 = HBSEL: 0 FB: 20 FBS: 84 FC: 7679 NCF1: 29, NCF0: 255 - def set_frequency(self, freq_hz): - if (freq_hz<240E6) or (freq_hz>960E6): - print("Invalid Frequency!") - return False - - hbsel = 1 if (freq_hz>= 480E6) else 0 - - fb = int(freq_hz/10E6/(hbsel+1) - 24) - fbs = (1<<6) | (hbsel<<5) | fb - fc = int((freq_hz/(10.0E6 * (hbsel+1)) - fb - 24) * 64000) - - ncf1 = fc>>8 - ncf0 = fc & 0xFF - - print("hbsel: %d, fb: %d, fbs: %d, fc: %d, ncf1: %d, ncf0: %d" % (hbsel,fb,fbs,fc,ncf1,ncf0)) - - self.write_register(REG.FREQUENCY_BAND_SELECT,fbs) - self.write_register(REG.NOMINAL_CARRIER_FREQUENCY_1,ncf1) - self.write_register(REG.NOMINAL_CARRIER_FREQUENCY_0,ncf0) - - # Read back registers. - fbs_read = self.read_register(REG.FREQUENCY_BAND_SELECT) - ncf1_read = self.read_register(REG.NOMINAL_CARRIER_FREQUENCY_1) - ncf0_read = self.read_register(REG.NOMINAL_CARRIER_FREQUENCY_0) - - if (fbs == fbs_read) and (ncf1 == ncf1_read) and (ncf0 == ncf0_read): - print("Frequency set OK!") - return True - else: - print("Frequency not set correctly!") - return False - - def setup_basic(self): - # Software reset. - self.write_register(REG.OPERATING_FUNCTION_CONTROL_1,0x80) - time.sleep(0.1) - # Interrupts - self.write_register(REG.INTERRUPT_ENABLE_1,0x87) - # Switch to ready mode. - self.write_register(REG.OPERATING_FUNCTION_CONTROL_1,0x01) - self.write_register(REG.CRYSTAL_OSCILLATOR_LOAD,0x7f) - # Configure GPIO 0 and 1 settings. - self.write_register(REG.GPIO0_CONFIGURATION, 0x12) # TX State, physically wired into TX_ANT pin. - self.write_register(REG.GPIO1_CONFIGURATION, 0x15) # RX State, physically wired into RX_ANT pin. - - # Program a bulk set of registers. - # I'm using this to set a bunch of different registers at one time, to load up pre-calculated configs. - def set_bulk(self,registers,data): - if len(registers) != len(data): - print("Array length mismatch!") - return False - - for k in xrange(len(registers)): - self.write_register(registers[k],data[k]) - - def set_tx_power(self,power): - self.write_register(REG.TX_POWER,power) - - def set_mode(self,mode): - self.write_register(REG.OPERATING_FUNCTION_CONTROL_1,mode) - -if __name__ == '__main__': - - # Attempt to extract a frequency from the first argument: - if len(sys.argv)<2: - print("Usage: \tpython init_rfm22b.py ") - print("Example: \tpython init_rfm22b.py 441.200") - sys.exit(1) - - try: - tx_freq = float(sys.argv[1]) - except: - print("Unable to parse input.") - - if tx_freq>450.0 or tx_freq<430.00: - print("Frequency out of 70cm band, using default.") - tx_freq = 441.200*1e6 - else: - tx_freq = tx_freq*1e6 - - rfm = RFM22B() - rfm.set_tx_power(TXPOW.TXPOW_17DBM | 0x08) - rfm.set_frequency(tx_freq) - rfm.write_register(REG.GPIO2_CONFIGURATION,0x30) # TX Data In - rfm.set_bulk(CONFIGS.REGISTERS,CONFIGS.DIRECT_120K) # Direct Asynchronous mode, ~120KHz tone spacing. - rfm.set_mode(0x09) - rfm.close() - diff --git a/tx/init_rfm98w.py b/tx/init_rfm98w.py deleted file mode 100644 index 66ef945..0000000 --- a/tx/init_rfm98w.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python -# -# RFM98W Initialisation for Wenet Telemetry -# -# Copyright (C) 2018 Mark Jessop -# Released under GNU GPL v3 or later -# -# Requires pySX127x: -# https://github.com/darksidelemm/pySX127x -# Copy the SX127x directory into this directory... -# -# Uses spidev for comms with a RFM98W module. -# -# Note: As with the RFM22B version, all this script does -# is get the RFM98W onto the right frequency, and into the right mode. -# -# SPI: Connected to CE0 (like most of my LoRa shields) -# RPi TXD: Connected to RFM98W's DIO2 pin. -# -import sys -import argparse -from SX127x.LoRa import * -from SX127x.hardware_piloragateway import HardwareInterface - -def setup_rfm98w(frequency=441.200, spi_device=0, shutdown=False, deviation = 71797): - # Set this to 1 if your RFM98W is on CE1 - hw = HardwareInterface(spi_device) - - # Start talking to the module... - lora = LoRa(hw) - - # Set us into FSK mode, and set continuous mode on - lora.set_register(0x01,0x00) # Standby Mode - # If we have been asked to shutdown the RFM98W, then exit here. - if shutdown: - sys.exit(0) - - # Otherwise, proceed. - lora.set_register(0x31,0x00) # Set Continuous Mode - - # Set TX Frequency - lora.set_freq(frequency) - - # 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 - - lora.set_register(0x04,_dev_msb) - lora.set_register(0x05,_dev_lsb) - - # Set Transmit power to 50mW. - # NOTE: If you're in another country you'll probably want to modify this value to something legal... - lora.set_register(0x09,0x8F) - - # Go into TX mode. - lora.set_register(0x01,0x02) # .. via FSTX mode (where the transmit frequency actually gets set) - lora.set_register(0x01,0x03) # Now we're in TX mode... - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("--frequency", default=441.200, type=float, help="Transmit Frequency (MHz). Default = 441.200 MHz.") - parser.add_argument("--spidevice", default=0, type=int, help="LoRa SPI Device number. Default = 0.") - parser.add_argument("--baudrate", default=115200, type=int, help="Wenet TX baud rate. We chose a TX deviation based on this. Default=115200.") - parser.add_argument("--shutdown",action="store_true", help="Shutdown Transmitter instead of activating it.") - args = parser.parse_args() - - tx_freq = args.frequency - - if tx_freq>450.0 or tx_freq<430.00: - print("Frequency out of 70cm band, using default.") - tx_freq = 441.200 - - - if args.baudrate == 9600: - deviation = 4800 - elif args.baudrate == 4800: - deviation = 2400 - else: - deviation = 71797 - - print("Using deviation of %d Hz for Baud Rate %d bd." % (deviation, args.baudrate)) - - setup_rfm98w(frequency=tx_freq, spi_device=args.spidevice, shutdown=args.shutdown, deviation=deviation) - - print("Frequency set to: %.3f MHz" % tx_freq) - - sys.exit(0) - - diff --git a/tx/radio_wrappers.py b/tx/radio_wrappers.py new file mode 100644 index 0000000..093e769 --- /dev/null +++ b/tx/radio_wrappers.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python +# +# Radio Wrappers for Wenet Transmissions +# +# Copyright (C) 2024 Mark Jessop +# Released under GNU GPL v3 or later +# +# For RFM98W support, requires pySX127x: +# https://github.com/darksidelemm/pySX127x +# (This is included with the Wenet repository) +# +# Uses spidev for comms with a RFM98W module. +# +# Note: As with the RFM22B version, all this script does +# is get the RFM98W onto the right frequency, and into the right mode. +# +# SPI: Connected to CE0 (like most of my LoRa shields) +# RPi TXD: Connected to RFM98W's DIO2 pin. +# +import sys +import argparse +import logging +import serial +import time +import numpy as np +from SX127x.LoRa import * +from SX127x.hardware_piloragateway import HardwareInterface + + +class RFM98W_Serial(object): + """ + RFM98W Wrapper for Wenet Transmission, using 2-FSK Direct-Asynchronous Modulation via a UART. + """ + + def __init__( + self, + spidevice=0, + frequency=443.500, + baudrate=115200, + serial_port=None, + tx_power_dbm=17, + reinit_count=5000 + ): + + self.spidevice = spidevice + self.frequency = frequency + self.baudrate = baudrate + self.serial_port = serial_port + self.tx_power_dbm = tx_power_dbm + self.reinit_count = reinit_count + + self.hw = None + self.lora = None + + self.tx_packet_count = 0 + + 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. + """ + + self.hw = HardwareInterface(self.spidevice) + 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())}") + + if not self.comms_ok(): + logging.critical("RFM98W - No communication with RFM98W IC!") + self.shutdown() + return + + # Deviation selection. + if self.baudrate == 9600: + deviation = 4800 + elif self.baudrate == 4800: + deviation = 2400 + else: + # Default deviation, for 115200 baud + deviation = 71797 + + # Refer https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf + self.lora.set_register(0x01,0x00) # FSK Sleep Mode + self.lora.set_register(0x31,0x00) # Set Continuous Transmit Mode + + 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 + self.lora.set_register(0x04,_dev_msb) + self.lora.set_register(0x05,_dev_lsb) + + # Set Transmit power + 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.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.serial_port: + try: + self.serial = serial.Serial(self.serial_port, self.baudrate) + 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)}") + self.serial = None + + else: + # If no serial port info provided, write out to a binary debug file. + self.serial = BinaryDebug() + logging.info("No serial port provided - using Binary Debug output (binary_debug.bin)") + + + + 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 serial connection + self.serial.close() + logging.info("RFM98W - Closed Serial Port") + self.serial = 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 serial data, using a UART. + """ + if self.serial: + self.serial.write(packet) + + # 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() + + + +class BinaryDebug(object): + """ Debug binary 'transmitter' Class + Used to write packet data to a file in one-bit-per-char (i.e. 0 = 0x00, 1 = 0x01) + format for use with codec2-dev's fsk modulator. + Useful for debugging, that's about it. + """ + def __init__(self): + self.f = open("binary_debug.bin",'wb') + + def write(self,data): + # 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)) + raw_data = np.concatenate((raw_data,[0],d_array[::-1],[1])) + + self.f.write(raw_data.astype(np.uint8).tostring()) + + def close(self): + self.f.close() + + +if __name__ == '__main__': + + # Test code for the above. Allows enabling a radio and (optionally) sending some test packets. + + 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("--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.") + 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("--shutdown", default=False, action="store_true", help="Shutdown Transmitter after configuration.") + parser.add_argument("--test_modulation", default=False, action="store_true", help="Transmit a sequence of dummy packets as a test.") + parser.add_argument("-v", "--verbose", action='store_true', default=False, help="Show additional debug info.") + args = parser.parse_args() + + if args.verbose: + logging_level = logging.DEBUG + else: + logging_level = logging.INFO + + # Set up logging + logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s", level=logging_level) + + radio = None + + if args.rfm98w is not None: + radio = RFM98W_Serial( + spidevice = args.rfm98w, + frequency = args.frequency, + baudrate = args.baudrate, + serial_port = args.serial_port, + tx_power_dbm = args.tx_power + ) + # Other radio options would go here. + else: + logging.critical("No radio type specified! Exiting") + sys.exit(1) + + if args.test_modulation: + # Transmit a canned text message a few times + time.sleep(1) + test_packet = b'UUUUUUUUUUUUUUUU\xab\xcd\xef\x01\x00\x1d\x00\x01This is a Wenet test message!UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU5\x89\xad5\xff\xfbgX\x96\xaa\x10\xb9\x05,\x8co\xf7\xf0\xdd\x19\x1bs2\xd9$\x85\xa2\xc2\xd5\xc9\x15\xef\xac\x06\xb6\x11H\xb0;\xc3\xae\x1b\xe0_\x8cC\x13L*\x04\x17(\x9a\xa6\x95\x84\xf1UB{\xf5\x96\xb9\x14\x05\xa8@' + logging.info("Sending 100 test packets.") + for x in range(100): + radio.transmit_packet(test_packet) + time.sleep(0.1) + + if args.shutdown: + logging.info("Sleeping 5 seconds before shutdown") + time.sleep(5) + radio.shutdown() + + sys.exit(0) + + diff --git a/tx/tx_picamera2_gps.py b/tx/tx_picamera2_gps.py index bb37ffb..36aa6a8 100644 --- a/tx/tx_picamera2_gps.py +++ b/tx/tx_picamera2_gps.py @@ -3,7 +3,7 @@ # PiCamera2 Library Transmitter Script - with GPS Data and Logo Overlay. # Capture images from the PiCam, and transmit them. # -# Copyright (C) 2023 Mark Jessop +# Copyright (C) 2024 Mark Jessop # Released under GNU GPL v3 or later # @@ -11,19 +11,40 @@ import PacketTX import WenetPiCamera2 import ublox import argparse +import logging import time import os import subprocess import traceback +from radio_wrappers import * + parser = argparse.ArgumentParser() parser.add_argument("callsign", default="N0CALL", help="Payload Callsign") parser.add_argument("--gps", default="/dev/ttyACM0", help="uBlox GPS Serial port. Defaults to /dev/ttyACM0") parser.add_argument("--logo", default="none", help="Optional logo to overlay on image.") -parser.add_argument("--txport", default="/dev/ttyAMA0", type=str, help="Transmitter serial port. Defaults to /dev/ttyAMA0") -parser.add_argument("--baudrate", default=115200, type=int, help="Transmitter baud rate. Defaults to 115200 baud.") +parser.add_argument("--rfm98w", default=0, type=int, help="If set, configure a RFM98W on this SPI device number.") +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.") +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.") +parser.add_argument("--hflip", action='store_true', default=False, help="Flip captured image horizontally.") +parser.add_argument("--resize", type=float, default=0.5, help="Resize raw image from camera by this factor before transmit (in both X/Y, to nearest multiple of 16 pixels). Default=0.5") +parser.add_argument("--whitebalance", type=str, default='daylight', help="White Balance setting: Auto, Daylight, Cloudy, Incandescent, Tungesten, Fluorescent, Indoor") +parser.add_argument("--lensposition", type=float, default=-1, help="For PiCam v3, set the lens position. Default: -1 = Autofocus") +parser.add_argument("-v", "--verbose", action='store_true', default=False, help="Show additional debug info.") args = parser.parse_args() +if args.verbose: + logging_level = logging.DEBUG +else: + logging_level = logging.INFO + +# Set up logging +logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s", level=logging_level) + + callsign = args.callsign # Truncate callsign if it's too long. if len(callsign) > 6: @@ -31,9 +52,22 @@ if len(callsign) > 6: print("Using Callsign: %s" % callsign) +if args.rfm98w is not None: + radio = RFM98W_Serial( + spidevice = args.rfm98w, + frequency = args.frequency, + baudrate = args.baudrate, + serial_port = args.serial_port, + tx_power_dbm = args.tx_power + ) +# Other radio options would go here. +else: + logging.critical("No radio type specified! Exiting") + sys.exit(1) + # Start up Wenet TX. -tx = PacketTX.PacketTX(serial_port=args.txport, serial_baud=args.baudrate, callsign=callsign, log_file="debug.log", udp_listener=55674) +tx = PacketTX.PacketTX(radio=radio, callsign=callsign, log_file="debug.log", udp_listener=55674) tx.start_tx() # Sleep for a second to let the transmitter fire up. @@ -46,11 +80,12 @@ system_time_set = False # Disable Systemctl NTP synchronization so that we can set the system time on first GPS lock. # This is necessary as NTP will refuse to sync the system time to the information we feed it via ntpshm unless # the system clock is already within a few seconds. -ret_code = os.system("timedatectl set-ntp 0") -if ret_code == 0: - tx.transmit_text_message("GPS Debug: Disabled NTP Sync until GPS lock.") -else: - tx.transmit_text_message("GPS Debug: Could not disable NTP sync.") +if args.gps.lower() != 'none': + ret_code = os.system("timedatectl set-ntp 0") + if ret_code == 0: + tx.transmit_text_message("GPS Debug: Disabled NTP Sync until GPS lock.") + else: + tx.transmit_text_message("GPS Debug: Could not disable NTP sync.") def handle_gps_data(gps_data): """ Handle GPS data passed to us from the UBloxGPS instance """ @@ -87,14 +122,19 @@ def handle_gps_data(gps_data): # Try and start up the GPS rx thread. + try: - gps = ublox.UBloxGPS(port=args.gps, - dynamic_model = ublox.DYNAMIC_MODEL_AIRBORNE1G, - update_rate_ms = 1000, - debug_ptr = tx.transmit_text_message, - callback = handle_gps_data, - log_file = 'gps_data.log' - ) + if args.gps.lower() != 'none': + gps = ublox.UBloxGPS(port=args.gps, + dynamic_model = ublox.DYNAMIC_MODEL_AIRBORNE1G, + update_rate_ms = 1000, + debug_ptr = tx.transmit_text_message, + callback = handle_gps_data, + log_file = 'gps_data.log' + ) + else: + tx.transmit_text_message("No GPS configured. No GPS data will be overlaid.") + gps = None except Exception as e: tx.transmit_text_message("ERROR: Could not Open GPS - %s" % str(e), repeats=5) gps = None @@ -152,12 +192,14 @@ def post_process_image(filename): # Finally, initialise the PiCam capture object. picam = WenetPiCamera2.WenetPiCamera2( - tx_resolution=(1936,1088), + tx_resolution=args.resize, callsign=callsign, num_images=5, debug_ptr=tx.transmit_text_message, - vertical_flip=False, - horizontal_flip=False) + vertical_flip=args.vflip, + horizontal_flip=args.hflip, + whitebalance=args.whitebalance, + lens_position=args.lensposition) # .. and start it capturing continuously. picam.run(destination_directory="./tx_images/", tx = tx, @@ -177,7 +219,8 @@ except KeyboardInterrupt: print("Closing") picam.stop() tx.close() - gps.close() + if gps: + gps.close() diff --git a/tx/tx_test_images.py b/tx/tx_test_images.py index ab3334d..571092f 100644 --- a/tx/tx_test_images.py +++ b/tx/tx_test_images.py @@ -7,7 +7,8 @@ # Released under GNU GPL v3 or later # -import PacketTX, sys, os, argparse +import PacketTX, sys, os, argparse, logging +from radio_wrappers import * # Set to whatever resolution you want to test. file_path = "../test_images/%d_raw.bin" # _raw, _800x608, _640x480, _320x240 @@ -35,13 +36,46 @@ def transmit_file(filename, tx_object): tx_object.wait() + + parser = argparse.ArgumentParser() -parser.add_argument("--baudrate", default=115200, type=int, help="Transmitter baud rate. Defaults to 115200 baud.") +parser.add_argument("--rfm98w", default=None, type=int, help="If set, configure a RFM98W on this SPI device number.") +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.") +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("-v", "--verbose", action='store_true', default=False, help="Show additional debug info.") args = parser.parse_args() +if args.verbose: + logging_level = logging.DEBUG +else: + logging_level = logging.INFO + +# Set up logging +logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s", level=logging_level) + +radio = None + +if args.rfm98w is not None: + radio = RFM98W_Serial( + spidevice = args.rfm98w, + frequency = args.frequency, + baudrate = args.baudrate, + serial_port = args.serial_port, + tx_power_dbm = args.tx_power + ) +# Other radio options would go here. +else: + logging.critical("No radio type specified! Exiting") + sys.exit(1) + +tx = PacketTX.PacketTX( + radio=radio, + udp_listener=55674) -tx = PacketTX.PacketTX(debug=debug_output, serial_baud=args.baudrate) tx.start_tx() + print("TX Started. Press Ctrl-C to stop.") try: for img in image_numbers: diff --git a/wenet_tx.service b/wenet_tx.service index 0ba2406..3e8b9b8 100644 --- a/wenet_tx.service +++ b/wenet_tx.service @@ -3,8 +3,8 @@ Description=wenet_tx After=basic.target [Service] -# Update this path if not running as the pi user! -ExecStart=/home/pi/wenet/start_tx_systemd.sh +# Update this path if the wenet files are not in the pi user's home directory! +ExecStart=/home/pi/wenet/start_tx.sh Restart=always RestartSec=30 # This one too!