diff --git a/bands.json b/bands.json index 554659ec..0e9dc7bc 100644 --- a/bands.json +++ b/bands.json @@ -287,6 +287,15 @@ }, "tags": ["hamradio"] }, + { + "name": "AM Broadcast", + "lower_bound": 200000, + "upper_bound": 1700000, + "frequencies": { + "navtex": [ 490000, 518000 ] + }, + "tags": ["broadcast"] + }, { "name": "120m Broadcast", "lower_bound": 2300000, diff --git a/csdr/chain/digimodes.py b/csdr/chain/digimodes.py index ce078782..8a3a10b5 100644 --- a/csdr/chain/digimodes.py +++ b/csdr/chain/digimodes.py @@ -3,12 +3,12 @@ from csdr.module.msk144 import Msk144Module, ParserAdapter from owrx.audio.chopper import AudioChopper, AudioChopperParser from owrx.aprs.kiss import KissDeframer from owrx.aprs import Ax25Parser, AprsParser -from pycsdr.modules import Convert, FmDemod, Agc, TimingRecovery, DBPskDecoder, VaricodeDecoder, RttyDecoder, BaudotDecoder, Lowpass, MFRttyDecoder, CwDecoder, SstvDecoder, FaxDecoder, SitorBDecoder, Ccir476Decoder, DscDecoder, Ccir493Decoder, Shift +from pycsdr.modules import Convert, FmDemod, Agc, TimingRecovery, DBPskDecoder, VaricodeDecoder, RttyDecoder, BaudotDecoder, Lowpass, MFRttyDecoder, CwDecoder, SstvDecoder, FaxDecoder, SitorBDecoder, Ccir476Decoder, DscDecoder, Ccir493Decoder, NavtexDecoder, Shift from pycsdr.types import Format from owrx.aprs.direwolf import DirewolfModule from owrx.sstv import SstvParser from owrx.fax import FaxParser -from owrx.dsc import DscParser +from owrx.marine import DscParser, NavtexParser from owrx.config import Config @@ -226,9 +226,8 @@ class FaxDemodulator(ServiceDemodulator, DialFrequencyReceiver): class SitorBDemodulator(SecondaryDemodulator, SecondarySelectorChain): def __init__(self, baudRate=100, bandWidth=170, invert=False): - self.baudRate = baudRate + self.baudRate = baudRate self.bandWidth = bandWidth - self.invert = invert # this is an assumption, we will adjust in setSampleRate self.sampleRate = self.bandWidth * 10 #12000 secondary_samples_per_bit = int(round(self.sampleRate / self.baudRate)) @@ -260,10 +259,9 @@ class SitorBDemodulator(SecondaryDemodulator, SecondarySelectorChain): class DscDemodulator(SecondaryDemodulator, SecondarySelectorChain, DialFrequencyReceiver): def __init__(self, baudRate=100, bandWidth=170, invert=False, service=False): - self.baudRate = baudRate - self.bandWidth = bandWidth - self.invert = invert - self.parser = DscParser(service=service) + self.baudRate = baudRate + self.bandWidth = bandWidth + self.parser = DscParser(service=service) # this is an assumption, we will adjust in setSampleRate self.sampleRate = self.bandWidth * 10 #12000 secondary_samples_per_bit = int(round(self.sampleRate / self.baudRate)) @@ -300,3 +298,47 @@ class DscDemodulator(SecondaryDemodulator, SecondarySelectorChain, DialFrequency # ServiceDemodulator def getFixedAudioRate(self): return self.sampleRate + + +class NavtexDemodulator(SecondaryDemodulator, SecondarySelectorChain, DialFrequencyReceiver): + def __init__(self, baudRate=100, bandWidth=170, invert=False, service=False): + self.baudRate = baudRate + self.bandWidth = bandWidth + self.parser = NavtexParser(service=service) + # this is an assumption, we will adjust in setSampleRate + self.sampleRate = self.bandWidth * 10 #12000 + secondary_samples_per_bit = int(round(self.sampleRate / self.baudRate)) + cutoff = self.baudRate / self.sampleRate + loop_gain = self.sampleRate / self.getBandwidth() / 5 + workers = [ + Agc(Format.COMPLEX_FLOAT), + FmDemod(), + Lowpass(Format.FLOAT, cutoff), + TimingRecovery(Format.FLOAT, secondary_samples_per_bit, loop_gain, 10), + SitorBDecoder(invert=invert), + Ccir476Decoder(), + NavtexDecoder(), + self.parser + ] + super().__init__(workers) + + def getBandwidth(self) -> float: + return self.bandWidth + + def setSampleRate(self, sampleRate: int) -> None: + if sampleRate == self.sampleRate: + return + self.sampleRate = sampleRate + secondary_samples_per_bit = int(round(self.sampleRate / self.baudRate)) + cutoff = self.baudRate / self.sampleRate + loop_gain = self.sampleRate / self.getBandwidth() / 5 + self.replace(2, Lowpass(Format.FLOAT, cutoff)) + self.replace(3, TimingRecovery(Format.FLOAT, secondary_samples_per_bit, loop_gain, 10)) + + # DialFrequencyReceiver + def setDialFrequency(self, frequency: int) -> None: + self.parser.setDialFrequency(frequency) + + # ServiceDemodulator + def getFixedAudioRate(self): + return self.sampleRate diff --git a/owrx/dsp.py b/owrx/dsp.py index bfb76009..651a5cab 100644 --- a/owrx/dsp.py +++ b/owrx/dsp.py @@ -698,6 +698,9 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient) elif mod == "sitorb": from csdr.chain.digimodes import SitorBDemodulator return SitorBDemodulator(100, 170 + 40) + elif mod == "navtex": + from csdr.chain.digimodes import NavtexDemodulator + return NavtexDemodulator(100, 170 + 40) elif mod == "dsc": from csdr.chain.digimodes import DscDemodulator return DscDemodulator(100, 170 + 40) diff --git a/owrx/dsc.py b/owrx/marine.py similarity index 87% rename from owrx/dsc.py rename to owrx/marine.py index 1742933d..0c517c76 100644 --- a/owrx/dsc.py +++ b/owrx/marine.py @@ -8,18 +8,19 @@ import logging logger = logging.getLogger(__name__) +class NavtexParser(TextParser): + def __init__(self, service: bool = False): + # Construct parent object + super().__init__(filePrefix="NAVTEX", service=service) + + class DscParser(TextParser): def __init__(self, service: bool = False): # Colors will be assigned via this cache self.colors = ColorCache() - # No frequency yet - self.frequency = 0 # Construct parent object super().__init__(filePrefix="DSC", service=service) - def setDialFrequency(self, frequency: int) -> None: - self.frequency = frequency - def parse(self, msg: bytes): # Do not parse in service mode #if self.service: diff --git a/owrx/modes.py b/owrx/modes.py index 5af72c6a..5881de8f 100644 --- a/owrx/modes.py +++ b/owrx/modes.py @@ -142,6 +142,7 @@ class Modes(object): DigitalMode("rtty450", "RTTY-450 (50N)", underlying=["usb", "lsb"]), DigitalMode("rtty85", "RTTY-85 (50N)", underlying=["usb", "lsb"]), DigitalMode("sitorb", "SITOR-B", underlying=["usb"]), + DigitalMode("navtex", "NAVTEX", underlying=["usb"], service=True), DigitalMode("dsc", "DSC", underlying=["usb"], service=True), WsjtMode("ft8", "FT8"), WsjtMode("ft4", "FT4"), diff --git a/owrx/service/__init__.py b/owrx/service/__init__.py index a6157b2b..7c5b9a44 100644 --- a/owrx/service/__init__.py +++ b/owrx/service/__init__.py @@ -326,6 +326,9 @@ class ServiceHandler(SdrSourceEventClient): elif mod == "fax": from csdr.chain.digimodes import FaxDemodulator return FaxDemodulator(service=True) + elif mod == "navtex": + from csdr.chain.digimodes import NavtexDemodulator + return NavtexDemodulator(100, 170 + 40, service=True) elif mod == "dsc": from csdr.chain.digimodes import DscDemodulator return DscDemodulator(100, 170 + 40, service=True)