add FST4 and FST4W modes
This commit is contained in:
		
							
								
								
									
										24
									
								
								csdr/csdr.py
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								csdr/csdr.py
									
									
									
									
									
								
							@@ -29,7 +29,7 @@ import math
 | 
			
		||||
from functools import partial
 | 
			
		||||
 | 
			
		||||
from owrx.kiss import KissClient, DirewolfConfig
 | 
			
		||||
from owrx.wsjt import Ft8Profile, WsprProfile, Jt9Profile, Jt65Profile, Ft4Profile
 | 
			
		||||
from owrx.wsjt import Ft8Profile, WsprProfile, Jt9Profile, Jt65Profile, Ft4Profile, Fst4Profile, Fst4wProfile
 | 
			
		||||
from owrx.js8 import Js8Profiles
 | 
			
		||||
from owrx.audio import AudioChopper
 | 
			
		||||
 | 
			
		||||
@@ -412,19 +412,23 @@ class dsp(object):
 | 
			
		||||
 | 
			
		||||
        if self.isWsjtMode():
 | 
			
		||||
            smd = self.get_secondary_demodulator()
 | 
			
		||||
            chopper_profile = None
 | 
			
		||||
            chopper_profiles = None
 | 
			
		||||
            if smd == "ft8":
 | 
			
		||||
                chopper_profile = Ft8Profile()
 | 
			
		||||
                chopper_profiles = [Ft8Profile()]
 | 
			
		||||
            elif smd == "wspr":
 | 
			
		||||
                chopper_profile = WsprProfile()
 | 
			
		||||
                chopper_profiles = [WsprProfile()]
 | 
			
		||||
            elif smd == "jt65":
 | 
			
		||||
                chopper_profile = Jt65Profile()
 | 
			
		||||
                chopper_profiles = [Jt65Profile()]
 | 
			
		||||
            elif smd == "jt9":
 | 
			
		||||
                chopper_profile = Jt9Profile()
 | 
			
		||||
                chopper_profiles = [Jt9Profile()]
 | 
			
		||||
            elif smd == "ft4":
 | 
			
		||||
                chopper_profile = Ft4Profile()
 | 
			
		||||
            if chopper_profile is not None:
 | 
			
		||||
                chopper = AudioChopper(self, self.secondary_process_demod.stdout, chopper_profile)
 | 
			
		||||
                chopper_profiles = [Ft4Profile()]
 | 
			
		||||
            elif smd == "fst4":
 | 
			
		||||
                chopper_profiles = Fst4Profile.getEnabledProfiles()
 | 
			
		||||
            elif smd == "fst4w":
 | 
			
		||||
                chopper_profiles = Fst4wProfile.getEnabledProfiles()
 | 
			
		||||
            if chopper_profiles is not None and len(chopper_profiles):
 | 
			
		||||
                chopper = AudioChopper(self, self.secondary_process_demod.stdout, *chopper_profiles)
 | 
			
		||||
                chopper.start()
 | 
			
		||||
                self.output.send_output("wsjt_demod", chopper.read)
 | 
			
		||||
        elif self.isJs8():
 | 
			
		||||
@@ -566,7 +570,7 @@ class dsp(object):
 | 
			
		||||
    def isWsjtMode(self, demodulator=None):
 | 
			
		||||
        if demodulator is None:
 | 
			
		||||
            demodulator = self.get_secondary_demodulator()
 | 
			
		||||
        return demodulator in ["ft8", "wspr", "jt65", "jt9", "ft4"]
 | 
			
		||||
        return demodulator in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w"]
 | 
			
		||||
 | 
			
		||||
    def isJs8(self, demodulator = None):
 | 
			
		||||
        if demodulator is None:
 | 
			
		||||
 
 | 
			
		||||
@@ -74,6 +74,8 @@ class Modes(object):
 | 
			
		||||
        DigitalMode(
 | 
			
		||||
            "wspr", "WSPR", underlying=["usb"], bandpass=Bandpass(1350, 1650), requirements=["wsjt-x"], service=True
 | 
			
		||||
        ),
 | 
			
		||||
        DigitalMode("fst4", "FST4", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True),
 | 
			
		||||
        DigitalMode("fst4w", "FST4W", underlying=["usb"], bandpass=Bandpass(1350, 1650), requirements=["wsjt-x"], service=True),
 | 
			
		||||
        DigitalMode("js8", "JS8Call", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["js8call"], service=True),
 | 
			
		||||
        DigitalMode(
 | 
			
		||||
            "packet",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										81
									
								
								owrx/wsjt.py
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								owrx/wsjt.py
									
									
									
									
									
								
							@@ -85,8 +85,52 @@ class Ft4Profile(WsjtProfile):
 | 
			
		||||
        return ["jt9", "--ft4", "-d", str(self.decoding_depth("ft4")), file]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Fst4Profile(WsjtProfile):
 | 
			
		||||
    availableIntervals = [15, 30, 60,  120, 300, 900, 1800]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, interval):
 | 
			
		||||
        self.interval = interval
 | 
			
		||||
 | 
			
		||||
    def getInterval(self):
 | 
			
		||||
        return self.interval
 | 
			
		||||
 | 
			
		||||
    def getFileTimestampFormat(self):
 | 
			
		||||
        return "%y%m%d_%H%M%S"
 | 
			
		||||
 | 
			
		||||
    def decoder_commandline(self, file):
 | 
			
		||||
        return ["jt9", "--fst4", "-b", "FST4-{0}".format(self.interval), "-d", str(self.decoding_depth("fst4")), file]
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def getEnabledProfiles():
 | 
			
		||||
        config = Config.get()
 | 
			
		||||
        profiles = config["fst4_enabled_intervals"] if "fst4_enabled_intervals" in config else []
 | 
			
		||||
        return [Fst4Profile(i) for i in profiles if i in Fst4Profile.availableIntervals]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Fst4wProfile(WsjtProfile):
 | 
			
		||||
    availableIntervals = [120, 300, 900, 1800]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, interval):
 | 
			
		||||
        self.interval = interval
 | 
			
		||||
 | 
			
		||||
    def getInterval(self):
 | 
			
		||||
        return self.interval
 | 
			
		||||
 | 
			
		||||
    def getFileTimestampFormat(self):
 | 
			
		||||
        return "%y%m%d_%H%M%S"
 | 
			
		||||
 | 
			
		||||
    def decoder_commandline(self, file):
 | 
			
		||||
        return ["jt9", "--fst4w", "-b", "FST4W-{0}".format(self.interval), "-d", str(self.decoding_depth("fst4w")), file]
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def getEnabledProfiles():
 | 
			
		||||
        config = Config.get()
 | 
			
		||||
        profiles = config["fst4w_enabled_intervals"] if "fst4w_enabled_intervals" in config else []
 | 
			
		||||
        return [Fst4Profile(i) for i in profiles if i in Fst4Profile.availableIntervals]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WsjtParser(Parser):
 | 
			
		||||
    modes = {"~": "FT8", "#": "JT65", "@": "JT9", "+": "FT4"}
 | 
			
		||||
    modes = {"~": "FT8", "#": "JT65", "@": "JT9", "+": "FT4", "`": "FST4"}
 | 
			
		||||
 | 
			
		||||
    def parse(self, messages):
 | 
			
		||||
        for data in messages:
 | 
			
		||||
@@ -115,7 +159,7 @@ class WsjtParser(Parser):
 | 
			
		||||
                        PskReporter.getSharedInstance().spot(out)
 | 
			
		||||
 | 
			
		||||
                self.handler.write_wsjt_message(out)
 | 
			
		||||
            except ValueError:
 | 
			
		||||
            except (ValueError, IndexError):
 | 
			
		||||
                logger.exception("error while parsing wsjt message")
 | 
			
		||||
 | 
			
		||||
    def pushDecode(self, mode):
 | 
			
		||||
@@ -139,6 +183,8 @@ class WsjtParser(Parser):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Decoder(ABC):
 | 
			
		||||
    locator_pattern = re.compile(".*\\s([A-Z0-9]+)\\s([A-R]{2}[0-9]{2})$")
 | 
			
		||||
 | 
			
		||||
    def parse_timestamp(self, instring, dateformat):
 | 
			
		||||
        ts = datetime.strptime(instring, dateformat)
 | 
			
		||||
        return int(
 | 
			
		||||
@@ -149,23 +195,36 @@ class Decoder(ABC):
 | 
			
		||||
    def parse(self, msg, dial_freq):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def parseMessage(self, msg):
 | 
			
		||||
        m = Decoder.locator_pattern.match(msg)
 | 
			
		||||
        if m is None:
 | 
			
		||||
            return {}
 | 
			
		||||
        # this is a valid locator in theory, but it's somewhere in the arctic ocean, near the north pole, so it's very
 | 
			
		||||
        # likely this just means roger roger goodbye.
 | 
			
		||||
        if m.group(2) == "RR73":
 | 
			
		||||
            return {"callsign": m.group(1)}
 | 
			
		||||
        return {"callsign": m.group(1), "locator": m.group(2)}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Jt9Decoder(Decoder):
 | 
			
		||||
    locator_pattern = re.compile(".*\\s([A-Z0-9]+)\\s([A-R]{2}[0-9]{2})$")
 | 
			
		||||
 | 
			
		||||
    def parse(self, msg, dial_freq):
 | 
			
		||||
        # ft8 sample
 | 
			
		||||
        # '222100 -15 -0.0  508 ~  CQ EA7MJ IM66'
 | 
			
		||||
        # jt65 sample
 | 
			
		||||
        # '2352  -7  0.4 1801 #  R0WAS R2ABM KO85'
 | 
			
		||||
        # '0003  -4  0.4 1762 #  CQ R2ABM KO85'
 | 
			
		||||
        # fst4 sample
 | 
			
		||||
        # '**** -23  0.6 3023 `  <...> <...> R 591631 BI53PV'
 | 
			
		||||
        modes = list(WsjtParser.modes.keys())
 | 
			
		||||
        if msg[19] in modes:
 | 
			
		||||
            dateformat = "%H%M"
 | 
			
		||||
        else:
 | 
			
		||||
            dateformat = "%H%M%S"
 | 
			
		||||
        timestamp = self.parse_timestamp(msg[0 : len(dateformat)], dateformat)
 | 
			
		||||
        msg = msg[len(dateformat) + 1 :]
 | 
			
		||||
        try:
 | 
			
		||||
            timestamp = self.parse_timestamp(msg[0 : len(dateformat)], dateformat)
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            timestamp = None
 | 
			
		||||
        msg = msg[len(dateformat) + 1:]
 | 
			
		||||
        modeChar = msg[14:15]
 | 
			
		||||
        mode = WsjtParser.modes[modeChar] if modeChar in WsjtParser.modes else "unknown"
 | 
			
		||||
        wsjt_msg = msg[17:53].strip()
 | 
			
		||||
@@ -181,16 +240,6 @@ class Jt9Decoder(Decoder):
 | 
			
		||||
        result.update(self.parseMessage(wsjt_msg))
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def parseMessage(self, msg):
 | 
			
		||||
        m = Jt9Decoder.locator_pattern.match(msg)
 | 
			
		||||
        if m is None:
 | 
			
		||||
            return {}
 | 
			
		||||
        # this is a valid locator in theory, but it's somewhere in the arctic ocean, near the north pole, so it's very
 | 
			
		||||
        # likely this just means roger roger goodbye.
 | 
			
		||||
        if m.group(2) == "RR73":
 | 
			
		||||
            return {"callsign": m.group(1)}
 | 
			
		||||
        return {"callsign": m.group(1), "locator": m.group(2)}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WsprDecoder(Decoder):
 | 
			
		||||
    wspr_splitter_pattern = re.compile("([A-Z0-9]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)")
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user