kopia lustrzana https://github.com/projecthorus/wenet
Many changes to tx code to better handle radio upsets. Move to libcamera2 as the default
rodzic
9959be4ccb
commit
3828ff03b5
91
start_tx.sh
91
start_tx.sh
|
@ -1,9 +1,9 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Wenet TX-side Initialisation Script
|
||||
# 2022-03-19 Mark Jessop <vk5qi@rfhead.net>
|
||||
# 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!
|
||||
# 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
|
||||
|
|
|
@ -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
|
|
@ -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")
|
||||
logging.info("Closing")
|
|
@ -72,7 +72,7 @@ def setter(register_address):
|
|||
|
||||
############################################### Definition of the LoRa class ###########################################
|
||||
|
||||
class LoRa(object):
|
||||
class LoRaRFM98W(object):
|
||||
|
||||
BOARD = None
|
||||
spi = None
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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 <vk5qi@rfhead.net>
|
||||
# Copyright (C) 2024 Mark Jessop <vk5qi@rfhead.net>
|
||||
# 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()
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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!
|
||||
|
|
Ładowanie…
Reference in New Issue