kopia lustrzana https://github.com/projecthorus/radiosonde_auto_rx
				
				
				
			Added: usb_reset ability, updates to use new rtlsdr bias tee -T options, oziplotter push, hopefully better frequency scanning.
							rodzic
							
								
									a350d1ad5b
								
							
						
					
					
						commit
						c217f39f36
					
				| 
						 | 
				
			
			@ -15,6 +15,7 @@ Features:
 | 
			
		|||
* Uploading to:
 | 
			
		||||
  * APRS, with user-definable position comment.
 | 
			
		||||
  * Habitat
 | 
			
		||||
  * OziPlotter (Project Horus Offline Mapping)
 | 
			
		||||
 | 
			
		||||
Dependencies
 | 
			
		||||
------------
 | 
			
		||||
| 
						 | 
				
			
			@ -23,8 +24,8 @@ Dependencies
 | 
			
		|||
  * numpy
 | 
			
		||||
  * crcmod
 | 
			
		||||
* Also needs (grab from apt-get):
 | 
			
		||||
  * rtl-sdr
 | 
			
		||||
  * sox
 | 
			
		||||
* If you wish to use an RTLSDR with a bias tee, you will need a version of rtl-sdr which is newer than 2017-06-11. This may me having to grab the latest git version from: https://github.com/osmocom/rtl-sdr
 | 
			
		||||
 | 
			
		||||
Usage
 | 
			
		||||
-----
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,8 +22,6 @@
 | 
			
		|||
#
 | 
			
		||||
# TODO:
 | 
			
		||||
# [ ] Fix user gain setting issues. (gain='automatic' = no decode?!)
 | 
			
		||||
# [ ] Better peak signal detection. (Maybe convolve known spectral masks over power data?)
 | 
			
		||||
#   [ ] Figure out better quantization settings.
 | 
			
		||||
# [ ] Use FSK demod from codec2-dev ? 
 | 
			
		||||
# [ ] Storage of flight information in some kind of database.
 | 
			
		||||
# [ ] Local frequency blacklist, to speed up scan times.
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +39,7 @@ import subprocess
 | 
			
		|||
import traceback
 | 
			
		||||
from aprs_utils import *
 | 
			
		||||
from habitat_utils import *
 | 
			
		||||
from ozi_utils import *
 | 
			
		||||
from threading import Thread
 | 
			
		||||
from StringIO import StringIO
 | 
			
		||||
from findpeaks import *
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +55,10 @@ HABITAT_OUTPUT_ENABLED = False
 | 
			
		|||
INTERNET_PUSH_RUNNING = True
 | 
			
		||||
internet_push_queue = Queue.Queue()
 | 
			
		||||
 | 
			
		||||
# Second Queue for OziPlotter outputs, since we want this to run at a faster rate.
 | 
			
		||||
OZI_PUSH_RUNNING = True
 | 
			
		||||
ozi_push_queue = Queue.Queue()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Flight Statistics data
 | 
			
		||||
# stores copies of the telemetry dictionary returned by process_rs_line.
 | 
			
		||||
| 
						 | 
				
			
			@ -66,16 +69,20 @@ flight_stats = {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_rtl_power(start, stop, step, filename="log_power.csv",  dwell = 20, ppm = 0, gain = 'automatic'):
 | 
			
		||||
def run_rtl_power(start, stop, step, filename="log_power.csv",  dwell = 20, ppm = 0, gain = 'automatic', bias = False):
 | 
			
		||||
    """ Run rtl_power, with a timeout"""
 | 
			
		||||
    # rtl_power -f 400400000:403500000:800 -i20 -1 log_power.csv
 | 
			
		||||
 | 
			
		||||
    rtl_power_cmd = "timeout %d rtl_power -f %d:%d:%d -i %d -1 -p %d %s" % (dwell+10, start, stop, step, dwell, int(ppm), filename)
 | 
			
		||||
    # Add a -T option if bias is enabled
 | 
			
		||||
    bias_option = "-T " if bias else ""
 | 
			
		||||
 | 
			
		||||
    # Added -k 5 option, to SIGKILL rtl_power 5 seconds after the regular timeout expires. 
 | 
			
		||||
    rtl_power_cmd = "timeout -k 5 %d rtl_power %s-f %d:%d:%d -i %d -1 -c 20%% -p %d %s" % (dwell+10, bias_option, start, stop, step, dwell, int(ppm), filename)
 | 
			
		||||
    logging.info("Running frequency scan.")
 | 
			
		||||
    ret_code = os.system(rtl_power_cmd)
 | 
			
		||||
    if ret_code == 1:
 | 
			
		||||
        logging.critical("rtl_power call failed!")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
        return False
 | 
			
		||||
    else:
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -110,14 +117,19 @@ def read_rtl_power(filename):
 | 
			
		|||
        freq_step = float(fields[4])
 | 
			
		||||
        n_samples = int(fields[5])
 | 
			
		||||
 | 
			
		||||
        freq_range = np.arange(start_freq,stop_freq,freq_step)
 | 
			
		||||
        #freq_range = np.arange(start_freq,stop_freq,freq_step)
 | 
			
		||||
        samples = np.loadtxt(StringIO(",".join(fields[6:])),delimiter=',')
 | 
			
		||||
        freq_range = np.linspace(start_freq,stop_freq,len(samples))
 | 
			
		||||
 | 
			
		||||
        # Add frequency range and samples to output buffers.
 | 
			
		||||
        freq = np.append(freq, freq_range)
 | 
			
		||||
        power = np.append(power, samples)
 | 
			
		||||
 | 
			
		||||
    f.close()
 | 
			
		||||
 | 
			
		||||
    # Sanitize power values, to remove the nan's that rtl_power puts in there occasionally.
 | 
			
		||||
    power = np.nan_to_num(power)
 | 
			
		||||
 | 
			
		||||
    return (freq, power, freq_step)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -128,16 +140,15 @@ def quantize_freq(freq_list, quantize=5000):
 | 
			
		|||
def detect_sonde(frequency, ppm=0, gain='automatic', bias=False):
 | 
			
		||||
    """ Receive some FM and attempt to detect the presence of a radiosonde. """
 | 
			
		||||
 | 
			
		||||
    rx_test_command = "timeout 10s rtl_fm -p %d -M fm -s 15k -f %d 2>/dev/null |" % (int(ppm), frequency) 
 | 
			
		||||
    # Add a -T option if bias is enabled
 | 
			
		||||
    bias_option = "-T " if bias else ""
 | 
			
		||||
 | 
			
		||||
    rx_test_command = "timeout 10s rtl_fm %s-p %d -M fm -s 15k -f %d 2>/dev/null |" % (bias_option, int(ppm), frequency) 
 | 
			
		||||
    rx_test_command += "sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -t wav - highpass 20 2>/dev/null |"
 | 
			
		||||
    rx_test_command += "./rs_detect -z -t 8 2>/dev/null"
 | 
			
		||||
 | 
			
		||||
    logging.info("Attempting sonde detection on %.3f MHz" % (frequency/1e6))
 | 
			
		||||
 | 
			
		||||
    # Enable Bias-Tee if required.
 | 
			
		||||
    if bias:
 | 
			
		||||
        os.system('rtl_biast -b 1')
 | 
			
		||||
 | 
			
		||||
    ret_code = os.system(rx_test_command)
 | 
			
		||||
 | 
			
		||||
    ret_code = ret_code >> 8
 | 
			
		||||
| 
						 | 
				
			
			@ -151,6 +162,30 @@ def detect_sonde(frequency, ppm=0, gain='automatic', bias=False):
 | 
			
		|||
    else:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
def reset_rtlsdr():
 | 
			
		||||
    """ Attempt to perform a USB Reset on all attached RTLSDRs. This uses the usb_reset binary from ../scan"""
 | 
			
		||||
    lsusb_output = subprocess.check_output(['lsusb'])
 | 
			
		||||
    try:
 | 
			
		||||
        devices = lsusb_output.split('\n')
 | 
			
		||||
        for device in devices:
 | 
			
		||||
            if 'RTL2838' in device:
 | 
			
		||||
                # Found an rtlsdr! Attempt to extract bus and device number.
 | 
			
		||||
                # Expecting something like: 'Bus 001 Device 005: ID 0bda:2838 Realtek Semiconductor Corp. RTL2838 DVB-T'
 | 
			
		||||
                device_fields = device.split(' ')
 | 
			
		||||
                # Attempt to cast fields to integers, to give some surety that we have the correct data.
 | 
			
		||||
                device_bus = int(device_fields[1])
 | 
			
		||||
                device_number = int(device_fields[3][:-1])
 | 
			
		||||
                # Construct device address
 | 
			
		||||
                reset_argument = '/dev/bus/usb/%03d/%03d' % (device_bus, device_number)
 | 
			
		||||
                # Attempt to reset the device.
 | 
			
		||||
                logging.info("Resetting device: %s" % reset_argument)
 | 
			
		||||
                ret_code = subprocess.call(['./reset_usb', reset_argument])
 | 
			
		||||
                logging.debug("Got return code: %s" % ret_code)
 | 
			
		||||
            else:
 | 
			
		||||
                continue
 | 
			
		||||
    except:
 | 
			
		||||
        logging.error("Errors occured while attempting to reset USB device.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def sonde_search(config, attempts = 5):
 | 
			
		||||
    """ Perform a frequency scan across the defined range, and test each frequency for a radiosonde's presence. """
 | 
			
		||||
| 
						 | 
				
			
			@ -161,19 +196,19 @@ def sonde_search(config, attempts = 5):
 | 
			
		|||
 | 
			
		||||
    while search_attempts > 0:
 | 
			
		||||
 | 
			
		||||
        # Enable Bias-Tee if required.
 | 
			
		||||
        if config['rtlsdr_bias']:
 | 
			
		||||
            os.system('rtl_biast -b 1')
 | 
			
		||||
 | 
			
		||||
        # Scan Band
 | 
			
		||||
        run_rtl_power(config['min_freq']*1e6, config['max_freq']*1e6, config['search_step'], ppm=config['rtlsdr_ppm'], gain=config['rtlsdr_gain'])
 | 
			
		||||
        run_rtl_power(config['min_freq']*1e6, config['max_freq']*1e6, config['search_step'], ppm=config['rtlsdr_ppm'], gain=config['rtlsdr_gain'], bias=config['rtlsdr_bias'])
 | 
			
		||||
 | 
			
		||||
        # Read in result
 | 
			
		||||
        try:
 | 
			
		||||
            (freq, power, step) = read_rtl_power('log_power.csv')
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            traceback.print_exc()
 | 
			
		||||
            logging.debug("Failed to read log_power.csv. Attempting to run rtl_power again.")
 | 
			
		||||
            logging.error("Failed to read log_power.csv. Resetting RTLSDRs and attempting to run rtl_power again.")
 | 
			
		||||
            # no log_power.csv usually means that rtl_power has locked up and had to be SIGKILL'd. 
 | 
			
		||||
            # This occurs when it can't get samples from the RTLSDR, because it's locked up for some reason.
 | 
			
		||||
            # Issuing a USB Reset to the rtlsdr can sometimes solve this. 
 | 
			
		||||
            reset_rtlsdr()
 | 
			
		||||
            search_attempts -= 1
 | 
			
		||||
            time.sleep(10)
 | 
			
		||||
            continue
 | 
			
		||||
| 
						 | 
				
			
			@ -238,6 +273,13 @@ def process_rs_line(line):
 | 
			
		|||
        rs_frame['id'] = str(params[1])
 | 
			
		||||
        rs_frame['date'] = str(params[2])
 | 
			
		||||
        rs_frame['time'] = str(params[3])
 | 
			
		||||
        # Provide a clipped time field, without the milliseconds components.
 | 
			
		||||
        # Do this just by splitting off everything before the '.', if it exists.
 | 
			
		||||
        if '.' in rs_frame['time']:
 | 
			
		||||
            rs_frame['short_time'] = rs_frame['time'].split('.')[0]
 | 
			
		||||
        else:
 | 
			
		||||
            rs_frame['short_time'] = rs_frame['time']
 | 
			
		||||
 | 
			
		||||
        rs_frame['datetime_str'] = "%sT%s" % (rs_frame['date'], rs_frame['time'])
 | 
			
		||||
        rs_frame['lat'] = float(params[4])
 | 
			
		||||
        rs_frame['lon'] = float(params[5])
 | 
			
		||||
| 
						 | 
				
			
			@ -250,7 +292,7 @@ def process_rs_line(line):
 | 
			
		|||
        rs_frame['temp'] = 0.0
 | 
			
		||||
        rs_frame['humidity'] = 0.0
 | 
			
		||||
 | 
			
		||||
        logging.info("TELEMETRY: %s,%d,%s,%.5f,%.5f,%.1f" % (rs_frame['id'], rs_frame['frame'],rs_frame['time'], rs_frame['lat'], rs_frame['lon'], rs_frame['alt']))
 | 
			
		||||
        logging.info("TELEMETRY: %s,%d,%s,%.5f,%.5f,%.1f,%s" % (rs_frame['id'], rs_frame['frame'],rs_frame['time'], rs_frame['lat'], rs_frame['lon'], rs_frame['alt'], rs_frame['crc']))
 | 
			
		||||
 | 
			
		||||
        return rs_frame
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -276,6 +318,8 @@ def update_flight_stats(data):
 | 
			
		|||
    if data['alt'] > flight_stats['apogee']['alt']:
 | 
			
		||||
        flight_stats['apogee'] = data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def calculate_flight_statistics():
 | 
			
		||||
    """ Produce a flight summary, for inclusion in the log file. """
 | 
			
		||||
    global flight_stats
 | 
			
		||||
| 
						 | 
				
			
			@ -310,7 +354,7 @@ def calculate_flight_statistics():
 | 
			
		|||
 | 
			
		||||
def decode_rs92(frequency, ppm=0, gain='automatic', bias=False, rx_queue=None, almanac=None, ephemeris=None, timeout=120):
 | 
			
		||||
    """ Decode a RS92 sonde """
 | 
			
		||||
    global latest_sonde_data
 | 
			
		||||
    global latest_sonde_data, internet_push_queue, ozi_push_queue
 | 
			
		||||
 | 
			
		||||
    # Before we get started, do we need to download GPS data?
 | 
			
		||||
    if ephemeris == None:
 | 
			
		||||
| 
						 | 
				
			
			@ -328,7 +372,10 @@ def decode_rs92(frequency, ppm=0, gain='automatic', bias=False, rx_queue=None, a
 | 
			
		|||
            logging.critical("Could not obtain GPS ephemeris or almanac data.")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    decode_cmd = "rtl_fm -p %d -M fm -s 12k -f %d 2>/dev/null |" % (int(ppm), frequency)
 | 
			
		||||
    # Add a -T option if bias is enabled
 | 
			
		||||
    bias_option = "-T " if bias else ""
 | 
			
		||||
 | 
			
		||||
    decode_cmd = "rtl_fm %s-p %d -M fm -s 12k -f %d 2>/dev/null |" % (bias_option, int(ppm), frequency)
 | 
			
		||||
    decode_cmd += "sox -t raw -r 12k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - lowpass 2500 highpass 20 2>/dev/null |"
 | 
			
		||||
 | 
			
		||||
    # Note: I've got the check-CRC option hardcoded in here as always on. 
 | 
			
		||||
| 
						 | 
				
			
			@ -341,10 +388,6 @@ def decode_rs92(frequency, ppm=0, gain='automatic', bias=False, rx_queue=None, a
 | 
			
		|||
 | 
			
		||||
    rx_last_line = time.time()
 | 
			
		||||
 | 
			
		||||
    # Enable Bias-Tee if required.
 | 
			
		||||
    if bias:
 | 
			
		||||
        os.system('rtl_biast -b 1')
 | 
			
		||||
 | 
			
		||||
    # Receiver subprocess. Discard stderr, and feed stdout into an asynchronous read class.
 | 
			
		||||
    rx = subprocess.Popen(decode_cmd, shell=True, stdin=None, stdout=subprocess.PIPE, preexec_fn=os.setsid) 
 | 
			
		||||
    rx_stdout = AsynchronousFileReader(rx.stdout, autostart=True)
 | 
			
		||||
| 
						 | 
				
			
			@ -366,7 +409,8 @@ def decode_rs92(frequency, ppm=0, gain='automatic', bias=False, rx_queue=None, a
 | 
			
		|||
 | 
			
		||||
                        if rx_queue != None:
 | 
			
		||||
                            try:
 | 
			
		||||
                                rx_queue.put_nowait(data)
 | 
			
		||||
                                internet_push_queue.put_nowait(data)
 | 
			
		||||
                                ozi_push_queue.put_nowait(data)
 | 
			
		||||
                            except:
 | 
			
		||||
                                pass
 | 
			
		||||
                except:
 | 
			
		||||
| 
						 | 
				
			
			@ -389,8 +433,11 @@ def decode_rs92(frequency, ppm=0, gain='automatic', bias=False, rx_queue=None, a
 | 
			
		|||
 | 
			
		||||
def decode_rs41(frequency, ppm=0, gain='automatic', bias=False, rx_queue=None, timeout=120):
 | 
			
		||||
    """ Decode a RS41 sonde """
 | 
			
		||||
    global latest_sonde_data
 | 
			
		||||
    decode_cmd = "rtl_fm -p %d -M fm -s 12k -f %d 2>/dev/null |" % (int(ppm), frequency)
 | 
			
		||||
    global latest_sonde_data, internet_push_queue, ozi_push_queue
 | 
			
		||||
    # Add a -T option if bias is enabled
 | 
			
		||||
    bias_option = "-T " if bias else ""
 | 
			
		||||
 | 
			
		||||
    decode_cmd = "rtl_fm %s-p %d -M fm -s 12k -f %d 2>/dev/null |" % (bias_option, int(ppm), frequency)
 | 
			
		||||
    decode_cmd += "sox -t raw -r 12k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - lowpass 2600 2>/dev/null |"
 | 
			
		||||
 | 
			
		||||
    # Note: I've got the check-CRC option hardcoded in here as always on. 
 | 
			
		||||
| 
						 | 
				
			
			@ -400,10 +447,6 @@ def decode_rs41(frequency, ppm=0, gain='automatic', bias=False, rx_queue=None, t
 | 
			
		|||
 | 
			
		||||
    rx_last_line = time.time()
 | 
			
		||||
 | 
			
		||||
    # Enable Bias-Tee if required.
 | 
			
		||||
    if bias:
 | 
			
		||||
        os.system('rtl_biast -b 1')
 | 
			
		||||
 | 
			
		||||
    # Receiver subprocess. Discard stderr, and feed stdout into an asynchronous read class.
 | 
			
		||||
    rx = subprocess.Popen(decode_cmd, shell=True, stdin=None, stdout=subprocess.PIPE, preexec_fn=os.setsid) 
 | 
			
		||||
    rx_stdout = AsynchronousFileReader(rx.stdout, autostart=True)
 | 
			
		||||
| 
						 | 
				
			
			@ -421,11 +464,14 @@ def decode_rs41(frequency, ppm=0, gain='automatic', bias=False, rx_queue=None, t
 | 
			
		|||
                        data['freq'] = "%.3f MHz" % (frequency/1e6)
 | 
			
		||||
                        data['type'] = "RS41"
 | 
			
		||||
 | 
			
		||||
                        update_flight_stats(data)
 | 
			
		||||
 | 
			
		||||
                        latest_sonde_data = data
 | 
			
		||||
 | 
			
		||||
                        if rx_queue != None:
 | 
			
		||||
                            try:
 | 
			
		||||
                                rx_queue.put_nowait(data)
 | 
			
		||||
                                internet_push_queue.put_nowait(data)
 | 
			
		||||
                                ozi_push_queue.put_nowait(data)
 | 
			
		||||
                            except:
 | 
			
		||||
                                pass
 | 
			
		||||
                except:
 | 
			
		||||
| 
						 | 
				
			
			@ -494,11 +540,40 @@ def internet_push_thread(station_config):
 | 
			
		|||
            # Note that this will result in some odd upload times, due to leap seconds and otherwise, but should
 | 
			
		||||
            # result in multiple stations (assuming local timezones are the same, and the stations are synced to NTP)
 | 
			
		||||
            # uploading at roughly the same time.
 | 
			
		||||
            while int(time.time())%config['upload_rate'] != 0:
 | 
			
		||||
            while int(time.time())%station_config['upload_rate'] != 0:
 | 
			
		||||
                time.sleep(0.1)
 | 
			
		||||
        else:
 | 
			
		||||
            # Otherwise, just sleep.
 | 
			
		||||
            time.sleep(config['upload_rate'])
 | 
			
		||||
            time.sleep(station_config['upload_rate'])
 | 
			
		||||
 | 
			
		||||
    print("Closing thread.")
 | 
			
		||||
 | 
			
		||||
def ozi_push_thread(station_config):
 | 
			
		||||
    """ Push a frame of sonde data into various internet services (APRS-IS, Habitat) """
 | 
			
		||||
    global ozi_push_queue, OZI_PUSH_RUNNING
 | 
			
		||||
    print("Started OziPlotter Push thread.")
 | 
			
		||||
    while OZI_PUSH_RUNNING:
 | 
			
		||||
        data = None
 | 
			
		||||
        try:
 | 
			
		||||
            # Wait until there is somethign in the queue before trying to process.
 | 
			
		||||
            if ozi_push_queue.empty():
 | 
			
		||||
                time.sleep(1)
 | 
			
		||||
                continue
 | 
			
		||||
            else:
 | 
			
		||||
                # Read in entire contents of queue, and keep the most recent entry.
 | 
			
		||||
                while not ozi_push_queue.empty():
 | 
			
		||||
                    data = ozi_push_queue.get()
 | 
			
		||||
        except:
 | 
			
		||||
            traceback.print_exc()
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            if station_config['ozi_enabled']:
 | 
			
		||||
                push_telemetry_to_ozi(data,hostname=station_config['ozi_hostname'])
 | 
			
		||||
        except:
 | 
			
		||||
            traceback.print_exc()
 | 
			
		||||
 | 
			
		||||
        time.sleep(station_config['ozi_update_rate'])
 | 
			
		||||
 | 
			
		||||
    print("Closing thread.")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -533,7 +608,8 @@ if __name__ == "__main__":
 | 
			
		|||
    timeout_time = time.time() + int(args.timeout)*60
 | 
			
		||||
 | 
			
		||||
    # Internet push thread object.
 | 
			
		||||
    push_thread = None
 | 
			
		||||
    push_thread_1 = None
 | 
			
		||||
    push_thread_2 = None
 | 
			
		||||
 | 
			
		||||
    # Sonde Frequency & Type variables.
 | 
			
		||||
    sonde_freq = None
 | 
			
		||||
| 
						 | 
				
			
			@ -562,10 +638,14 @@ if __name__ == "__main__":
 | 
			
		|||
 | 
			
		||||
        logging.info("Starting decoding of %s on %.3f MHz" % (sonde_type, sonde_freq/1e6))
 | 
			
		||||
 | 
			
		||||
        # Start a thread to push data to the web, if it isn't started already.
 | 
			
		||||
        if push_thread == None:
 | 
			
		||||
            push_thread = Thread(target=internet_push_thread, kwargs={'station_config':config})
 | 
			
		||||
            push_thread.start()
 | 
			
		||||
        # Start both of our internet/ozi push threads.
 | 
			
		||||
        if push_thread_1 == None:
 | 
			
		||||
            push_thread_1 = Thread(target=internet_push_thread, kwargs={'station_config':config})
 | 
			
		||||
            push_thread_1.start()
 | 
			
		||||
 | 
			
		||||
        if push_thread_2 == None:
 | 
			
		||||
            push_thread_2 = Thread(target=ozi_push_thread, kwargs={'station_config':config})
 | 
			
		||||
            push_thread_2.start()
 | 
			
		||||
 | 
			
		||||
        # Start decoding the sonde!
 | 
			
		||||
        if sonde_type == "RS92":
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@
 | 
			
		|||
# Build rs_detect.
 | 
			
		||||
cd ../scan/
 | 
			
		||||
gcc rs_detect.c -lm -o rs_detect
 | 
			
		||||
gcc reset_usb.c -o reset_usb
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Build rs92 and rs41 decoders
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +24,7 @@ gcc rs_main92.o rs_rs92.o rs_bch_ecc.o rs_demod.o rs_datum.o -lm -o rs92mod
 | 
			
		|||
# Copy all necessary files into this directory.
 | 
			
		||||
cd ../auto_rx/
 | 
			
		||||
cp ../scan/rs_detect .
 | 
			
		||||
cp ../scan/reset_usb .
 | 
			
		||||
cp ../rs_module/rs41mod .
 | 
			
		||||
cp ../rs_module/rs92mod .
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,7 +34,10 @@ def read_auto_rx_config(filename):
 | 
			
		|||
		'payload_callsign': 'RADIOSONDE',
 | 
			
		||||
		'uploader_callsign': 'SONDE_AUTO_RX',
 | 
			
		||||
		'uploader_lat' 	: 0.0,
 | 
			
		||||
		'uploader_lon'	: 0.0
 | 
			
		||||
		'uploader_lon'	: 0.0,
 | 
			
		||||
		'ozi_enabled'	: False,
 | 
			
		||||
		'ozi_update_rate': 5,
 | 
			
		||||
		'ozi_hostname'	: '127.0.0.1'
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	try:
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +68,9 @@ def read_auto_rx_config(filename):
 | 
			
		|||
		auto_rx_config['uploader_callsign'] = config.get('habitat', 'uploader_callsign')
 | 
			
		||||
		auto_rx_config['uploader_lat'] = config.getfloat('habitat', 'uploader_lat')
 | 
			
		||||
		auto_rx_config['uploader_lon'] = config.getfloat('habitat', 'uploader_lon')
 | 
			
		||||
		auto_rx_config['ozi_enabled'] = config.getboolean('oziplotter', 'ozi_enabled')
 | 
			
		||||
		auto_rx_config['ozi_update_rate'] = config.getint('oziplotter', 'ozi_update_rate')
 | 
			
		||||
		auto_rx_config['ozi_hostname'] = config.get('oziplotter', 'ozi_hostname')
 | 
			
		||||
 | 
			
		||||
		return auto_rx_config
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
# OziPlotter push utils for Sonde auto RX.
 | 
			
		||||
 | 
			
		||||
import socket
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Network Settings
 | 
			
		||||
HORUS_UDP_PORT = 55672
 | 
			
		||||
HORUS_OZIPLOTTER_PORT = 8942
 | 
			
		||||
 | 
			
		||||
# Send an update on the core payload telemetry statistics into the network via UDP broadcast.
 | 
			
		||||
# This can be used by other devices hanging off the network to display vital stats about the payload.
 | 
			
		||||
def send_payload_summary(callsign, latitude, longitude, altitude, speed=-1, heading=-1):
 | 
			
		||||
    packet = {
 | 
			
		||||
        'type' : 'PAYLOAD_SUMMARY',
 | 
			
		||||
        'callsign' : callsign,
 | 
			
		||||
        'latitude' : latitude,
 | 
			
		||||
        'longitude' : longitude,
 | 
			
		||||
        'altitude' : altitude,
 | 
			
		||||
        'speed' : speed,
 | 
			
		||||
        'heading': heading
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # Set up our UDP socket
 | 
			
		||||
    s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 | 
			
		||||
    s.settimeout(1)
 | 
			
		||||
    # Set up socket for broadcast, and allow re-use of the address
 | 
			
		||||
    s.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
 | 
			
		||||
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 | 
			
		||||
    try:
 | 
			
		||||
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
 | 
			
		||||
    except:
 | 
			
		||||
        pass
 | 
			
		||||
    s.bind(('',HORUS_UDP_PORT))
 | 
			
		||||
    try:
 | 
			
		||||
        s.sendto(json.dumps(packet), ('<broadcast>', HORUS_UDP_PORT))
 | 
			
		||||
    except socket.error:
 | 
			
		||||
        s.sendto(json.dumps(packet), ('127.0.0.1', HORUS_UDP_PORT))
 | 
			
		||||
 | 
			
		||||
# The new 'generic' OziPlotter upload function, with no callsign, or checksumming (why bother, really)
 | 
			
		||||
def oziplotter_upload_basic_telemetry(time, latitude, longitude, altitude, hostname="192.168.88.2"):
 | 
			
		||||
    sentence = "TELEMETRY,%s,%.5f,%.5f,%d\n" % (time, latitude, longitude, altitude)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        ozisock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 | 
			
		||||
        ozisock.sendto(sentence,(hostname,HORUS_OZIPLOTTER_PORT))
 | 
			
		||||
        ozisock.close()
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print("Failed to send to Ozi: " % e)
 | 
			
		||||
 | 
			
		||||
# Call both of the above functions, with radiosonde telemetry data.
 | 
			
		||||
def push_telemetry_to_ozi(telemetry, hostname='127.0.0.1'):
 | 
			
		||||
	# Payload data summary.
 | 
			
		||||
	send_payload_summary(telemetry['id'], telemetry['lat'], telemetry['lon'], telemetry['alt'])
 | 
			
		||||
 | 
			
		||||
	# Telemetry to OziPlotter
 | 
			
		||||
	oziplotter_upload_basic_telemetry(telemetry['short_time'], telemetry['lat'], telemetry['lon'], telemetry['alt'], hostname=hostname)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +41,7 @@ upload_rate = 30
 | 
			
		|||
# Upload when (seconds_since_utc_epoch%upload_rate) == 0. Otherwise just delay upload_rate seconds between uploads.
 | 
			
		||||
# Setting this to True with multple uploaders should give a higher chance of all uploaders uploading the same frame,
 | 
			
		||||
# however the upload_rate should not be set too low, else there may be a chance of missing upload slots.
 | 
			
		||||
synchronous_upload = False
 | 
			
		||||
synchronous_upload = True
 | 
			
		||||
# Enable upload to various services.
 | 
			
		||||
enable_aprs = False
 | 
			
		||||
enable_habitat = False
 | 
			
		||||
| 
						 | 
				
			
			@ -80,4 +80,9 @@ uploader_callsign = SONDE_AUTO_RX
 | 
			
		|||
uploader_lat = 0.0
 | 
			
		||||
uploader_lon = 0.0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Settings for pushing data into OziPlotter
 | 
			
		||||
# Oziplotter receives data via a basic CSV format, via UDP.
 | 
			
		||||
[oziplotter]
 | 
			
		||||
ozi_enabled = False
 | 
			
		||||
ozi_update_rate = 5
 | 
			
		||||
ozi_hostname = 127.0.0.1
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue