Many changes to tx code to better handle radio upsets. Move to libcamera2 as the default

pull/16/head
Mark Jessop 2024-09-14 21:12:27 +09:30
rodzic 9959be4ccb
commit 3828ff03b5
11 zmienionych plików z 542 dodań i 512 usunięć

Wyświetl plik

@ -1,9 +1,9 @@
#!/bin/bash #!/bin/bash
# #
# Wenet TX-side Initialisation Script # Wenet TX-side Initialisation Script - Systemd Unit Version
# 2022-03-19 Mark Jessop <vk5qi@rfhead.net> # 2024-07-21 Mark Jessop <vk5qi@rfhead.net>
# #
# 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. # Replace the transmit frequency and callsign with your own.
# #
@ -11,31 +11,92 @@
# This MUST be <= 6 characters long. # This MUST be <= 6 characters long.
MYCALL=N0CALL MYCALL=N0CALL
# The centre frequency of the Wenet transmission. # The centre frequency of the Wenet transmission, in MHz.
TXFREQ=443.500 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 # Baud Rate
# Known working transmit baud rates are 115200 (the preferred default). # 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 # Lower baud rates *may* work, but will need a lot of testing on the receiver
# chain to be sure they perform correctly. # chain to be sure they perform correctly.
BAUDRATE=115200 BAUDRATE=115200
# GPS Port # RFM98W SPI Device
# Note that we only support uBlox GPS units # SPI device number of your RFM98W chip
GPSPORT=/dev/ttyACM0 # 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. # CHANGE THE FOLLOWING LINE TO REFLECT THE ACTUAL PATH TO THE TX FOLDER.
# i.e. it may be /home/username/dev/wenet/tx/ # i.e. it may be /home/username/dev/wenet/tx/
cd /home/pi/wenet/tx/ cd /home/pi/wenet/tx/
#Uncomment to initialise a RFM22B (untested with Python 3) # Wait here until the SPI devices are available.
#python init_rfm22b.py $TXFREQ # This can take a few tens of seconds after boot.
# Uncomment for use with a RFM98W timeout=20
python3 init_rfm98w.py --frequency $TXFREQ --baudrate $BAUDRATE 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. # 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: python3 tx_picamera2_gps.py \
# python WenetPiCam.py --baudrate $BAUDRATE $MYCALL & --rfm98w $SPIDEVICE \
--baudrate $BAUDRATE \
--frequency $TXFREQ \
--serial_port $SERIALPORT \
--tx_power $TXPOWER \
--gps $GPSPORT \
--resize $TX_IMAGE_SCALING \
--whitebalance $WHITEBALANCE \
--vflip --hflip \
$MYCALL

Wyświetl plik

@ -1,65 +0,0 @@
#!/bin/bash
#
# Wenet TX-side Initialisation Script - Systemd Unit Version
# 2024-07-21 Mark Jessop <vk5qi@rfhead.net>
#
# 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

Wyświetl plik

@ -28,6 +28,7 @@ from time import sleep
from threading import Thread from threading import Thread
import numpy as np import numpy as np
from ldpc_encoder import * from ldpc_encoder import *
from radio_wrappers import *
from queue import Queue from queue import Queue
class PacketTX(object): class PacketTX(object):
@ -75,23 +76,16 @@ class PacketTX(object):
# WARNING: 115200 baud is ACTUALLY 115386.834 baud, as measured using a freq counter. # WARNING: 115200 baud is ACTUALLY 115386.834 baud, as measured using a freq counter.
def __init__(self, def __init__(self,
serial_port="/dev/ttyAMA0", # Radio wrapper, for radio setup and modulation.
serial_baud=115200, radio,
# User callsign, should be used in the idle sequence, but currently isn't...
callsign="N0CALL",
payload_length=256, payload_length=256,
fec=True, fec=True,
debug = False,
callsign="N0CALL",
udp_listener = None, udp_listener = None,
log_file = None): log_file = None):
# Instantiate our low-level transmit interface, be it a serial port, or the BinaryDebug class. self.radio = radio
if debug == True:
self.s = BinaryDebug()
self.debug = True
else:
self.debug = False
self.s = serial.Serial(serial_port,serial_baud)
self.payload_length = payload_length self.payload_length = payload_length
self.callsign = callsign.encode('ascii') self.callsign = callsign.encode('ascii')
@ -159,19 +153,16 @@ class PacketTX(object):
while self.transmit_active: while self.transmit_active:
if self.telemetry_queue.qsize()>0: if self.telemetry_queue.qsize()>0:
packet = self.telemetry_queue.get_nowait() packet = self.telemetry_queue.get_nowait()
self.s.write(packet) self.radio.transmit_packet(packet)
elif self.ssdv_queue.qsize()>0: elif self.ssdv_queue.qsize()>0:
packet = self.ssdv_queue.get_nowait() packet = self.ssdv_queue.get_nowait()
self.s.write(packet) self.radio.transmit_packet(packet)
else: else:
if not self.debug: self.radio.transmit_packet(self.idle_message)
self.s.write(self.idle_message) time.sleep(0.1)
else:
# TODO: Tune this value.
sleep(0.05)
print("Closing Thread") print("Closing Thread")
self.s.close() self.radio.shutdown()
def close(self): def close(self):
@ -534,16 +525,39 @@ if __name__ == "__main__":
import argparse import argparse
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--txport", default="/dev/ttyAMA0", type=str, help="Transmitter serial port. Defaults to /dev/ttyAMA0") parser.add_argument("--rfm98w", default=None, type=int, help="If set, configure a RFM98W on this SPI device number.")
parser.add_argument("--baudrate", default=115200, type=int, help="Transmitter baud rate. Defaults to 115200 baud.") 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() 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( tx = PacketTX(
debug=debug_output, radio=radio,
serial_port=args.txport,
serial_baud=args.baudrate,
udp_listener=55674) udp_listener=55674)
tx.start_tx() tx.start_tx()
@ -556,4 +570,4 @@ if __name__ == "__main__":
except KeyboardInterrupt: except KeyboardInterrupt:
tx.close() tx.close()
print("Closing") logging.info("Closing")

Wyświetl plik

@ -72,7 +72,7 @@ def setter(register_address):
############################################### Definition of the LoRa class ########################################### ############################################### Definition of the LoRa class ###########################################
class LoRa(object): class LoRaRFM98W(object):
BOARD = None BOARD = None
spi = None spi = None

Wyświetl plik

@ -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, def __init__(self,
callsign = "N0CALL", callsign = "N0CALL",
tx_resolution=(1936,1088), tx_resolution=(1936,1088),
@ -42,7 +53,7 @@ class WenetPiCamera2(object):
image_delay=0.0, image_delay=0.0,
vertical_flip = False, vertical_flip = False,
horizontal_flip = False, horizontal_flip = False,
greyworld = False, whitebalance = 'auto',
lens_position = 0.0, lens_position = 0.0,
temp_filename_prefix = 'picam_temp', temp_filename_prefix = 'picam_temp',
debug_ptr = None debug_ptr = None
@ -64,7 +75,7 @@ class WenetPiCamera2(object):
vertical_flip: Flip captured images vertically. vertical_flip: Flip captured images vertically.
horizontal_flip: Flip captured images horizontally. horizontal_flip: Flip captured images horizontally.
Used to correct for picam orientation. 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. lens_position: Lens Position setting (float), 0.0 = Infinity, 10 = very close.
Only usable on Pi Camera v3 modules. Only usable on Pi Camera v3 modules.
@ -81,12 +92,17 @@ class WenetPiCamera2(object):
self.num_images = num_images self.num_images = num_images
self.image_delay = image_delay self.image_delay = image_delay
self.callsign = callsign self.callsign = callsign
self.tx_resolution = tx_resolution self.tx_resolution_init = tx_resolution
self.horizontal_flip = horizontal_flip self.horizontal_flip = horizontal_flip
self.vertical_flip = vertical_flip self.vertical_flip = vertical_flip
self.greyworld = greyworld
self.lens_position = lens_position 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() self.init_camera()
@ -96,7 +112,18 @@ class WenetPiCamera2(object):
self.camera_properties = self.cam.camera_properties 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. # Configure camera, including flip settings.
capture_config = self.cam.create_still_configuration( capture_config = self.cam.create_still_configuration(
@ -105,14 +132,15 @@ class WenetPiCamera2(object):
self.cam.configure(capture_config) self.cam.configure(capture_config)
# Set other settings, White Balance, exposure metering, etc. # Set other settings, White Balance, exposure metering, etc.
self.cam.set_controls( self.cam.set_controls(
{'AwbMode': controls.AwbModeEnum.Daylight, {'AwbMode': self.whitebalance,
'AeMeteringMode': controls.AeMeteringModeEnum.Matrix, 'AeMeteringMode': controls.AeMeteringModeEnum.Matrix,
'NoiseReductionMode': controls.draft.NoiseReductionModeEnum.Off} 'NoiseReductionMode': controls.draft.NoiseReductionModeEnum.Off}
) )
# Set Pi Camera 3 lens position # 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.debug_ptr("Configured lens position to " + str(self.lens_position))
self.cam.set_controls({"AfMode": controls.AfModeEnum.Manual, "LensPosition": 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. # Set other settings, White Balance, exposure metering, etc.
self.cam.set_controls( self.cam.set_controls(
{'AwbMode': controls.AwbModeEnum.Daylight, {'AwbMode': self.whitebalance,
'AeMeteringMode': controls.AeMeteringModeEnum.Matrix, 'AeMeteringMode': controls.AeMeteringModeEnum.Matrix,
'NoiseReductionMode': controls.draft.NoiseReductionModeEnum.Off} 'NoiseReductionMode': controls.draft.NoiseReductionModeEnum.Off}
) )
# Set Pi Camera 3 lens position # 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.debug_ptr("Configured lens position to " + str(self.lens_position))
self.cam.set_controls({"AfMode": controls.AfModeEnum.Manual, "LensPosition": self.lens_position}) self.cam.set_controls({"AfMode": controls.AfModeEnum.Manual, "LensPosition": self.lens_position})

Wyświetl plik

@ -1,279 +0,0 @@
#!/usr/bin/env python
#
# RFM22B Initialisation for Horus High Speed Telemetry
#
# Copyright (C) 2018 Mark Jessop <vk5qi@rfhead.net>
# 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 <TX Frequency in MHz>")
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()

Wyświetl plik

@ -1,91 +0,0 @@
#!/usr/bin/env python
#
# RFM98W Initialisation for Wenet Telemetry
#
# Copyright (C) 2018 Mark Jessop <vk5qi@rfhead.net>
# 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)

Wyświetl plik

@ -0,0 +1,285 @@
#!/usr/bin/env python
#
# Radio Wrappers for Wenet Transmissions
#
# Copyright (C) 2024 Mark Jessop <vk5qi@rfhead.net>
# 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)

Wyświetl plik

@ -3,7 +3,7 @@
# PiCamera2 Library Transmitter Script - with GPS Data and Logo Overlay. # PiCamera2 Library Transmitter Script - with GPS Data and Logo Overlay.
# Capture images from the PiCam, and transmit them. # Capture images from the PiCam, and transmit them.
# #
# Copyright (C) 2023 Mark Jessop <vk5qi@rfhead.net> # Copyright (C) 2024 Mark Jessop <vk5qi@rfhead.net>
# Released under GNU GPL v3 or later # Released under GNU GPL v3 or later
# #
@ -11,19 +11,40 @@ import PacketTX
import WenetPiCamera2 import WenetPiCamera2
import ublox import ublox
import argparse import argparse
import logging
import time import time
import os import os
import subprocess import subprocess
import traceback import traceback
from radio_wrappers import *
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("callsign", default="N0CALL", help="Payload Callsign") 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("--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("--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("--rfm98w", default=0, type=int, help="If set, configure a RFM98W on this SPI device number.")
parser.add_argument("--baudrate", default=115200, type=int, help="Transmitter baud rate. Defaults to 115200 baud.") 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() 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 callsign = args.callsign
# Truncate callsign if it's too long. # Truncate callsign if it's too long.
if len(callsign) > 6: if len(callsign) > 6:
@ -31,9 +52,22 @@ if len(callsign) > 6:
print("Using Callsign: %s" % callsign) 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. # 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() tx.start_tx()
# Sleep for a second to let the transmitter fire up. # 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. # 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 # 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. # the system clock is already within a few seconds.
ret_code = os.system("timedatectl set-ntp 0") if args.gps.lower() != 'none':
if ret_code == 0: ret_code = os.system("timedatectl set-ntp 0")
tx.transmit_text_message("GPS Debug: Disabled NTP Sync until GPS lock.") if ret_code == 0:
else: tx.transmit_text_message("GPS Debug: Disabled NTP Sync until GPS lock.")
tx.transmit_text_message("GPS Debug: Could not disable NTP sync.") else:
tx.transmit_text_message("GPS Debug: Could not disable NTP sync.")
def handle_gps_data(gps_data): def handle_gps_data(gps_data):
""" Handle GPS data passed to us from the UBloxGPS instance """ """ 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 and start up the GPS rx thread.
try: try:
gps = ublox.UBloxGPS(port=args.gps, if args.gps.lower() != 'none':
dynamic_model = ublox.DYNAMIC_MODEL_AIRBORNE1G, gps = ublox.UBloxGPS(port=args.gps,
update_rate_ms = 1000, dynamic_model = ublox.DYNAMIC_MODEL_AIRBORNE1G,
debug_ptr = tx.transmit_text_message, update_rate_ms = 1000,
callback = handle_gps_data, debug_ptr = tx.transmit_text_message,
log_file = 'gps_data.log' 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: except Exception as e:
tx.transmit_text_message("ERROR: Could not Open GPS - %s" % str(e), repeats=5) tx.transmit_text_message("ERROR: Could not Open GPS - %s" % str(e), repeats=5)
gps = None gps = None
@ -152,12 +192,14 @@ def post_process_image(filename):
# Finally, initialise the PiCam capture object. # Finally, initialise the PiCam capture object.
picam = WenetPiCamera2.WenetPiCamera2( picam = WenetPiCamera2.WenetPiCamera2(
tx_resolution=(1936,1088), tx_resolution=args.resize,
callsign=callsign, callsign=callsign,
num_images=5, num_images=5,
debug_ptr=tx.transmit_text_message, debug_ptr=tx.transmit_text_message,
vertical_flip=False, vertical_flip=args.vflip,
horizontal_flip=False) horizontal_flip=args.hflip,
whitebalance=args.whitebalance,
lens_position=args.lensposition)
# .. and start it capturing continuously. # .. and start it capturing continuously.
picam.run(destination_directory="./tx_images/", picam.run(destination_directory="./tx_images/",
tx = tx, tx = tx,
@ -177,7 +219,8 @@ except KeyboardInterrupt:
print("Closing") print("Closing")
picam.stop() picam.stop()
tx.close() tx.close()
gps.close() if gps:
gps.close()

Wyświetl plik

@ -7,7 +7,8 @@
# Released under GNU GPL v3 or later # 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. # Set to whatever resolution you want to test.
file_path = "../test_images/%d_raw.bin" # _raw, _800x608, _640x480, _320x240 file_path = "../test_images/%d_raw.bin" # _raw, _800x608, _640x480, _320x240
@ -35,13 +36,46 @@ def transmit_file(filename, tx_object):
tx_object.wait() tx_object.wait()
parser = argparse.ArgumentParser() 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() 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() tx.start_tx()
print("TX Started. Press Ctrl-C to stop.") print("TX Started. Press Ctrl-C to stop.")
try: try:
for img in image_numbers: for img in image_numbers:

Wyświetl plik

@ -3,8 +3,8 @@ Description=wenet_tx
After=basic.target After=basic.target
[Service] [Service]
# Update this path if not running as the pi user! # Update this path if the wenet files are not in the pi user's home directory!
ExecStart=/home/pi/wenet/start_tx_systemd.sh ExecStart=/home/pi/wenet/start_tx.sh
Restart=always Restart=always
RestartSec=30 RestartSec=30
# This one too! # This one too!