diff --git a/bands.json b/bands.json
index 90e4c87..1629d4a 100644
--- a/bands.json
+++ b/bands.json
@@ -8,7 +8,8 @@
"ft8": 1840000,
"wspr": 1836600,
"jt65": 1838000,
- "jt9": 1839000
+ "jt9": 1839000,
+ "js8": 1842000
}
},
{
@@ -21,7 +22,8 @@
"wspr": 3592600,
"jt65": 3570000,
"jt9": 3572000,
- "ft4": [3568000, 3575000]
+ "ft4": [3568000, 3575000],
+ "js8": 3578000
}
},
{
@@ -43,7 +45,8 @@
"wspr": 7038600,
"jt65": 7076000,
"jt9": 7078000,
- "ft4": 7047500
+ "ft4": 7047500,
+ "js8": 7078000
}
},
{
@@ -56,7 +59,8 @@
"wspr": 10138700,
"jt65": 10138000,
"jt9": 10140000,
- "ft4": 10140000
+ "ft4": 10140000,
+ "js8": 10130000
}
},
{
@@ -69,7 +73,8 @@
"wspr": 14095600,
"jt65": 14076000,
"jt9": 14078000,
- "ft4": 14080000
+ "ft4": 14080000,
+ "js8": 14078000
}
},
{
@@ -82,7 +87,8 @@
"wspr": 18104600,
"jt65": 18102000,
"jt9": 18104000,
- "ft4": 18104000
+ "ft4": 18104000,
+ "js8": 18104000
}
},
{
@@ -95,7 +101,8 @@
"wspr": 21094600,
"jt65": 21076000,
"jt9": 21078000,
- "ft4": 21140000
+ "ft4": 21140000,
+ "js8": 21078000
}
},
{
@@ -108,7 +115,8 @@
"wspr": 24924600,
"jt65": 24917000,
"jt9": 24919000,
- "ft4": 24919000
+ "ft4": 24919000,
+ "js8": 24922000
}
},
{
@@ -121,7 +129,8 @@
"wspr": 28124600,
"jt65": 28076000,
"jt9": 28078000,
- "ft4": 28180000
+ "ft4": 28180000,
+ "js8": 28078000
}
},
{
@@ -134,7 +143,8 @@
"wspr": 50293000,
"jt65": 50310000,
"jt9": 50312000,
- "ft4": 50318000
+ "ft4": 50318000,
+ "js8": 50318000
}
},
{
diff --git a/csdr/csdr.py b/csdr/csdr.py
index 651944e..ac4dee3 100644
--- a/csdr/csdr.py
+++ b/csdr/csdr.py
@@ -30,6 +30,7 @@ from functools import partial
from owrx.kiss import KissClient, DirewolfConfig
from owrx.wsjt import Ft8Chopper, WsprChopper, Jt9Chopper, Jt65Chopper, Ft4Chopper
+from owrx.js8 import Js8Chopper
import logging
@@ -450,6 +451,7 @@ class dsp(object):
if self.isWsjtMode():
smd = self.get_secondary_demodulator()
chopper_cls = None
+ output_name = "wsjt_demod"
if smd == "ft8":
chopper_cls = Ft8Chopper
elif smd == "wspr":
@@ -460,10 +462,13 @@ class dsp(object):
chopper_cls = Jt9Chopper
elif smd == "ft4":
chopper_cls = Ft4Chopper
+ elif smd == "js8":
+ chopper_cls = Js8Chopper
+ output_name = "js8_demod"
if chopper_cls is not None:
chopper = chopper_cls(self, self.secondary_process_demod.stdout)
chopper.start()
- self.output.send_output("wsjt_demod", chopper.read)
+ self.output.send_output(output_name, chopper.read)
elif self.isPacket():
# we best get the ax25 packets from the kiss socket
kiss = KissClient(self.direwolf_port)
@@ -576,7 +581,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", "js8"]
def isPacket(self, demodulator=None):
if demodulator is None:
diff --git a/htdocs/index.html b/htdocs/index.html
index fefefbc..4ad78ef 100644
--- a/htdocs/index.html
+++ b/htdocs/index.html
@@ -193,6 +193,7 @@
+
diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js
index 52770c6..0c85f28 100644
--- a/htdocs/openwebrx.js
+++ b/htdocs/openwebrx.js
@@ -1305,7 +1305,7 @@ function update_wsjt_panel(msg) {
};
var linkedmsg = msg['msg'];
var matches;
- if (['FT8', 'JT65', 'JT9', 'FT4'].indexOf(msg['mode']) >= 0) {
+ if (['FT8', 'JT65', 'JT9', 'FT4', 'JS8'].indexOf(msg['mode']) >= 0) {
matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/);
if (matches && matches[2] !== 'RR73') {
linkedmsg = html_escape(matches[1]) + '' + matches[2] + '';
@@ -2019,6 +2019,7 @@ function demodulator_digital_replace(subtype) {
case "jt65":
case "jt9":
case "ft4":
+ case "js8":
secondary_demod_start(subtype);
demodulator_analog_replace('usb', true);
break;
@@ -2045,7 +2046,7 @@ function demodulator_digital_replace(subtype) {
demodulator_buttons_update();
$('#openwebrx-panel-digimodes').attr('data-mode', subtype);
toggle_panel("openwebrx-panel-digimodes", true);
- toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4'].indexOf(subtype) >= 0);
+ toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4', 'js8'].indexOf(subtype) >= 0);
toggle_panel("openwebrx-panel-packet-message", subtype === "packet");
toggle_panel("openwebrx-panel-pocsag-message", subtype === "pocsag");
updateHash();
diff --git a/owrx/dsp.py b/owrx/dsp.py
index 32b9116..d06a455 100644
--- a/owrx/dsp.py
+++ b/owrx/dsp.py
@@ -1,6 +1,6 @@
-from owrx.config import Config
from owrx.meta import MetaParser
from owrx.wsjt import WsjtParser
+from owrx.js8 import Js8Parser
from owrx.aprs import AprsParser
from owrx.pocsag import PocsagParser
from owrx.source import SdrSource
@@ -22,6 +22,7 @@ class DspManager(csdr.output):
"wsjt_demod": WsjtParser(self.handler),
"packet_demod": AprsParser(self.handler),
"pocsag_demod": PocsagParser(self.handler),
+ "js8_demod": Js8Parser(self.handler),
}
self.props = PropertyStack()
diff --git a/owrx/feature.py b/owrx/feature.py
index df2434b..7c7d457 100644
--- a/owrx/feature.py
+++ b/owrx/feature.py
@@ -40,6 +40,7 @@ class FeatureDetector(object):
"wsjt-x": ["wsjtx", "sox"],
"packet": ["direwolf", "sox"],
"pocsag": ["digiham", "sox"],
+ "js8call": ["js8", "sox"],
}
def feature_availability(self):
@@ -370,6 +371,12 @@ class FeatureDetector(object):
"""
return reduce(and_, map(self.command_is_runnable, ["jt9", "wsprd"]), True)
+ def has_js8(self):
+ """
+ To decode JS8, you will need to install [JS8Call](http://js8call.com/)
+ """
+ return self.command_is_runnable("js8")
+
def has_alsa(self):
"""
Some SDR receivers are identifying themselves as a soundcard. In order to read their data, OpenWebRX relies
diff --git a/owrx/js8.py b/owrx/js8.py
new file mode 100644
index 0000000..2042ad8
--- /dev/null
+++ b/owrx/js8.py
@@ -0,0 +1,32 @@
+from .wsjt import WsjtChopper
+from .parser import Parser
+import re
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class Js8Chopper(WsjtChopper):
+ def getInterval(self):
+ return 15
+
+ def getFileTimestampFormat(self):
+ return "%y%m%d_%H%M%S"
+
+ def decoder_commandline(self, file):
+ return ["js8", "--js8", "-d", str(self.decoding_depth("js8")), file]
+
+
+class Js8Parser(Parser):
+ decoderRegex = re.compile(" ?")
+
+ def parse(self, raw):
+ freq, raw_msg = raw
+ self.setDialFrequency(freq)
+ msg = raw_msg.decode().rstrip()
+ if Js8Parser.decoderRegex.match(msg):
+ return
+ if msg.startswith(" EOF on input file"):
+ return
+ logger.debug(msg)