Compare commits
71 Commits
image-2019
...
image-2019
Author | SHA1 | Date | |
---|---|---|---|
d4d8699fc5 | |||
e8d60e2dc0 | |||
944e9df7cc | |||
cd2da582c4 | |||
1e28fc5018 | |||
a24cb3e04a | |||
13f27a76ff | |||
39120d9413 | |||
fe08228204 | |||
c7eb5c430c | |||
70e2a99274 | |||
52b945cd64 | |||
07a8e6bf92 | |||
afa322a83b | |||
d3ac44c526 | |||
5bbee1e1d7 | |||
58da0e8a60 | |||
713b6119d0 | |||
ebf2804d63 | |||
3b77753829 | |||
eb29d0ac99 | |||
6cdec05cde | |||
7ef0ef0d7c | |||
dd7d262bd3 | |||
13d7686258 | |||
91b8c55de9 | |||
00c5467a89 | |||
cc32e28b36 | |||
72329a8a2a | |||
a102ee181a | |||
778591d460 | |||
6bc928b5b6 | |||
0b2c457030 | |||
93d4e629d1 | |||
d53d3b7a51 | |||
72062c8570 | |||
de90219406 | |||
de179d070d | |||
f45857f79b | |||
eda556ef03 | |||
ea67340cab | |||
5b61f8c7a3 | |||
70d8fe82b3 | |||
fce8c294d3 | |||
8541f79ebc | |||
ec4fd401cb | |||
98217b1745 | |||
378c574eed | |||
e5193f3460 | |||
60e90575ac | |||
78ffa6f184 | |||
f9f50e734f | |||
2e75bac90c | |||
8c2f081cb0 | |||
6adbc6c291 | |||
db663fe134 | |||
2e394dc2cb | |||
b80fd9c023 | |||
3e25f1ec42 | |||
351f63f0b8 | |||
9f90d01dc6 | |||
71d815cf08 | |||
a168136102 | |||
e9f9bbb9c0 | |||
3e8e2182a8 | |||
2025ccb366 | |||
6ae934e461 | |||
7431e4d7c0 | |||
eb0f54e79d | |||
08e9520019 | |||
630a542ed6 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
*.pyc
|
||||
*.swp
|
||||
tags
|
||||
.idea
|
||||
|
287
bookmarks.json
287
bookmarks.json
@ -1,65 +1,222 @@
|
||||
[{
|
||||
"name": "DB0ZU",
|
||||
"frequency": 145725000,
|
||||
"modulation": "nfm"
|
||||
},{
|
||||
"name": "DB0ZM",
|
||||
"frequency": 145750000,
|
||||
"modulation": "nfm"
|
||||
},{
|
||||
"name": "DM0ULR",
|
||||
"frequency": 145787500,
|
||||
"modulation": "nfm"
|
||||
},{
|
||||
"name": "DB0EL",
|
||||
"frequency": 439275000,
|
||||
"modulation": "nfm"
|
||||
},{
|
||||
"name": "DB0NJ",
|
||||
"frequency": 438775000,
|
||||
"modulation": "nfm"
|
||||
},{
|
||||
"name": "DB0NJ",
|
||||
"frequency": 439437500,
|
||||
"modulation": "dmr"
|
||||
},{
|
||||
"name": "DB0UFO",
|
||||
"frequency": 438312500,
|
||||
"modulation": "dmr"
|
||||
},{
|
||||
"name": "DB0PV",
|
||||
"frequency": 438525000,
|
||||
"modulation": "ysf"
|
||||
},{
|
||||
"name": "DB0BZA",
|
||||
"frequency": 438412500,
|
||||
"modulation": "ysf"
|
||||
},{
|
||||
"name": "DB0OSH",
|
||||
"frequency": 438250000,
|
||||
"modulation": "ysf"
|
||||
},{
|
||||
"name": "DB0ULR",
|
||||
"frequency": 439325000,
|
||||
"modulation": "nfm"
|
||||
},{
|
||||
"name": "DB0ZU",
|
||||
"frequency": 438850000,
|
||||
"modulation": "nfm"
|
||||
},{
|
||||
"name": "DB0ISW",
|
||||
"frequency": 438650000,
|
||||
"modulation": "nfm"
|
||||
},{
|
||||
"name": "Radio DARC",
|
||||
"frequency": 6070000,
|
||||
"modulation": "am"
|
||||
},{
|
||||
"name": "DB0TVM",
|
||||
"frequency": 439575000,
|
||||
"modulation": "dstar"
|
||||
},{
|
||||
"name": "DB0TVM",
|
||||
"frequency": 439800000,
|
||||
"modulation": "dmr"
|
||||
}]
|
||||
[
|
||||
{
|
||||
"name": "DB0ZU",
|
||||
"frequency": 145725000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0ZM",
|
||||
"frequency": 145750000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DM0ULR",
|
||||
"frequency": 145787500,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0EL",
|
||||
"frequency": 439275000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0NJ",
|
||||
"frequency": 438775000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0NJ",
|
||||
"frequency": 439437500,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "DB0UFO",
|
||||
"frequency": 438312500,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "DB0PV",
|
||||
"frequency": 438525000,
|
||||
"modulation": "ysf"
|
||||
},
|
||||
{
|
||||
"name": "DB0BZA",
|
||||
"frequency": 438412500,
|
||||
"modulation": "ysf"
|
||||
},
|
||||
{
|
||||
"name": "DB0OSH",
|
||||
"frequency": 438250000,
|
||||
"modulation": "ysf"
|
||||
},
|
||||
{
|
||||
"name": "DB0ULR",
|
||||
"frequency": 439325000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0ZU",
|
||||
"frequency": 438850000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0ISW",
|
||||
"frequency": 438650000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "Radio DARC",
|
||||
"frequency": 6070000,
|
||||
"modulation": "am"
|
||||
},
|
||||
{
|
||||
"name": "DB0TVM",
|
||||
"frequency": 439575000,
|
||||
"modulation": "dstar"
|
||||
},
|
||||
{
|
||||
"name": "DB0TVM",
|
||||
"frequency": 439800000,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "DB0TR",
|
||||
"frequency": 438700000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0PME",
|
||||
"frequency": 439825000,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "DB0HKN",
|
||||
"frequency": 438300000,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "OE2XHM",
|
||||
"frequency": 438825000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DM0WW",
|
||||
"frequency": 438962500,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "OE7XXR",
|
||||
"frequency": 438200000,
|
||||
"modulation": "dstar"
|
||||
},
|
||||
{
|
||||
"name": "OE2XZR",
|
||||
"frequency": 439000000,
|
||||
"modulation": "dstar"
|
||||
},
|
||||
{
|
||||
"name": "DB0OAL",
|
||||
"frequency": 439912500,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "DB0AAT",
|
||||
"frequency": 439550000,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "DB0FSG",
|
||||
"frequency": 439937500,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "Pocsag",
|
||||
"frequency": 439987500,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0ULR",
|
||||
"frequency": 145575000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0RDH",
|
||||
"frequency": 145737500,
|
||||
"modulation": "dstar"
|
||||
},
|
||||
{
|
||||
"name": "DM0GAP",
|
||||
"frequency": 145612500,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0XF",
|
||||
"frequency": 145600000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0TOL",
|
||||
"frequency": 145712500,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0TTB",
|
||||
"frequency": 439587500,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "DB0TRS",
|
||||
"frequency": 439125000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0OAL",
|
||||
"frequency": 438937500,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DM0ULR",
|
||||
"frequency": 439337500,
|
||||
"modulation": "nxdn"
|
||||
},
|
||||
{
|
||||
"name": "DB0MIR",
|
||||
"frequency": 439300000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0PM",
|
||||
"frequency": 439075000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0CP",
|
||||
"frequency": 439025000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "OE7XGR",
|
||||
"frequency": 438925000,
|
||||
"modulation": "dmr"
|
||||
},
|
||||
{
|
||||
"name": "DB0TOL",
|
||||
"frequency": 438725000,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0OAL",
|
||||
"frequency": 438325000,
|
||||
"modulation": "dstar"
|
||||
},
|
||||
{
|
||||
"name": "DB0ROL",
|
||||
"frequency": 439237500,
|
||||
"modulation": "nfm"
|
||||
},
|
||||
{
|
||||
"name": "DB0ABX",
|
||||
"frequency": 439137500,
|
||||
"modulation": "nfm"
|
||||
}
|
||||
]
|
||||
|
@ -41,13 +41,9 @@ max_clients = 20
|
||||
# ==== Web GUI configuration ====
|
||||
receiver_name = "[Callsign]"
|
||||
receiver_location = "Budapest, Hungary"
|
||||
receiver_qra = "JN97ML"
|
||||
receiver_asl = 200
|
||||
receiver_ant = "Longwire"
|
||||
receiver_device = "RTL-SDR"
|
||||
receiver_admin = "example@example.com"
|
||||
receiver_gps = (47.000000, 19.000000)
|
||||
photo_height = 350
|
||||
photo_title = "Panorama of Budapest from Schönherz Zoltán Dormitory"
|
||||
photo_desc = """
|
||||
You can add your own background photo and receiver information.<br />
|
||||
@ -181,17 +177,10 @@ sdrs = {
|
||||
},
|
||||
},
|
||||
},
|
||||
# this one is just here to test feature detection
|
||||
"test": {"type": "test"},
|
||||
}
|
||||
|
||||
# ==== Misc settings ====
|
||||
|
||||
client_audio_buffer_size = 5
|
||||
# increasing client_audio_buffer_size will:
|
||||
# - also increase the latency
|
||||
# - decrease the chance of audio underruns
|
||||
|
||||
iq_port_range = [
|
||||
4950,
|
||||
4960,
|
||||
|
17
csdr.py
17
csdr.py
@ -391,6 +391,15 @@ class dsp(object):
|
||||
def set_audio_compression(self, what):
|
||||
self.audio_compression = what
|
||||
|
||||
def get_audio_bytes_to_read(self):
|
||||
# desired latency: 5ms
|
||||
# uncompressed audio has 16 bits = 2 bytes per sample
|
||||
base = self.output_rate * 0.005 * 2
|
||||
# adpcm compresses the bitstream by 4
|
||||
if self.audio_compression == "adpcm":
|
||||
base = base / 4
|
||||
return int(base)
|
||||
|
||||
def set_fft_compression(self, what):
|
||||
self.fft_compression = what
|
||||
|
||||
@ -398,7 +407,7 @@ class dsp(object):
|
||||
if self.fft_compression == "none":
|
||||
return self.fft_size * 4
|
||||
if self.fft_compression == "adpcm":
|
||||
return (self.fft_size / 2) + (10 / 2)
|
||||
return int((self.fft_size / 2) + (10 / 2))
|
||||
|
||||
def get_secondary_fft_bytes_to_read(self):
|
||||
if self.fft_compression == "none":
|
||||
@ -455,8 +464,11 @@ class dsp(object):
|
||||
return demodulator == "packet"
|
||||
|
||||
def set_output_rate(self, output_rate):
|
||||
if self.output_rate == output_rate:
|
||||
return
|
||||
self.output_rate = output_rate
|
||||
self.calculate_decimation()
|
||||
self.restart()
|
||||
|
||||
def set_demodulator(self, demodulator):
|
||||
if self.demodulator == demodulator:
|
||||
@ -647,7 +659,8 @@ class dsp(object):
|
||||
self.output.send_output(
|
||||
"audio",
|
||||
partial(
|
||||
self.process.stdout.read, int(self.get_fft_bytes_to_read()) if self.demodulator == "fft" else 256
|
||||
self.process.stdout.read,
|
||||
self.get_fft_bytes_to_read() if self.demodulator == "fft" else self.get_audio_bytes_to_read(),
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -53,21 +53,13 @@
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#webrx-rx-avatar-background
|
||||
{
|
||||
cursor:pointer;
|
||||
background-image: url(../gfx/openwebrx-avatar-background.png);
|
||||
background-origin: content-box;
|
||||
background-repeat: no-repeat;
|
||||
float: left;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
padding: 7px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
#webrx-rx-avatar
|
||||
{
|
||||
background-color: rgba(154, 154, 154, .5);
|
||||
border-radius: 7px;
|
||||
float: left;
|
||||
margin: 7px;
|
||||
|
||||
cursor:pointer;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
|
@ -39,6 +39,8 @@ input[type=range]
|
||||
{
|
||||
-webkit-appearance: none;
|
||||
margin: 0 0;
|
||||
background: transparent;
|
||||
--track-background: #B6B6B6;
|
||||
}
|
||||
input[type=range]:focus
|
||||
{
|
||||
@ -54,6 +56,7 @@ input[type=range]::-webkit-slider-runnable-track
|
||||
background: #B6B6B6;
|
||||
/*border-radius: 11px;*/
|
||||
border: 1px solid #8A8A8A;
|
||||
background: var(--track-background);
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-thumb
|
||||
@ -72,6 +75,7 @@ input[type=range]::-webkit-slider-thumb
|
||||
input[type=range]:focus::-webkit-slider-runnable-track
|
||||
{
|
||||
background: #B6B6B6;
|
||||
background: var(--track-background);
|
||||
}
|
||||
|
||||
input[type=range]::-moz-range-track
|
||||
@ -81,6 +85,7 @@ input[type=range]::-moz-range-track
|
||||
animate: 0.2s;
|
||||
box-shadow: 0px 0px 0px #000000;
|
||||
background: #B6B6B6;
|
||||
background: var(--track-background);
|
||||
border-radius: 11px;
|
||||
border: 1px solid #8A8A8A;
|
||||
}
|
||||
@ -146,8 +151,10 @@ input[type=range]:focus::-ms-fill-upper
|
||||
|
||||
#webrx-page-container
|
||||
{
|
||||
min-height:100%;
|
||||
position:relative;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#openwebrx-scale-container
|
||||
@ -243,18 +250,23 @@ input[type=range]:focus::-ms-fill-upper
|
||||
border-top-color: #0FF;
|
||||
}
|
||||
|
||||
#openwebrx-bookmarks-container .bookmark[data-source=dial_frequencies] {
|
||||
background-color: #0F0;
|
||||
}
|
||||
|
||||
#openwebrx-bookmarks-container .bookmark[data-source=dial_frequencies]:after {
|
||||
border-top-color: #0F0;
|
||||
}
|
||||
|
||||
#webrx-canvas-container
|
||||
{
|
||||
/*background-image:url('../gfx/openwebrx-blank-background-1.jpg');*/
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
height: 2000px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
/*background-color: #646464;*/
|
||||
/*background-image: -webkit-linear-gradient(top, rgba(247,247,247,1) 0%, rgba(0,0,0,1) 100%);*/
|
||||
overflow: hidden;
|
||||
background-image: url('../gfx/openwebrx-background-cool-blue.png');
|
||||
background-repeat: no-repeat;
|
||||
background-color: #1e5f7f;
|
||||
background-size: cover;
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
@ -269,24 +281,11 @@ input[type=range]:focus::-ms-fill-upper
|
||||
|
||||
#openwebrx-mathbox-container
|
||||
{
|
||||
flex-grow: 1;
|
||||
overflow: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#openwebrx-phantom-canvas
|
||||
{
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
/*#openwebrx-canvas-gradient-background
|
||||
{
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 396px;
|
||||
}*/
|
||||
|
||||
#openwebrx-log-scroll
|
||||
{
|
||||
/*overflow-y:auto;*/
|
||||
@ -297,32 +296,12 @@ input[type=range]:focus::-ms-fill-upper
|
||||
.nano .nano-pane { background: #444; }
|
||||
.nano .nano-slider { background: #eee !important; }
|
||||
|
||||
#webrx-main-container
|
||||
{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.webrx-error
|
||||
{
|
||||
font-weight: bold;
|
||||
color: #ff6262;
|
||||
}
|
||||
|
||||
#openwebrx-problems span
|
||||
{
|
||||
background: #ff6262;
|
||||
padding: 3px;
|
||||
font-size: 8pt;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
margin: 0px 2px 0px 2px;
|
||||
}
|
||||
|
||||
/*#webrx-freq-show
|
||||
{
|
||||
visibility: hidden;
|
||||
@ -377,18 +356,35 @@ input[type=range]:focus::-ms-fill-upper
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#openwebrx-panels-container-left,
|
||||
#openwebrx-panels-container-right {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#openwebrx-panels-container-left {
|
||||
left: 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#openwebrx-panels-container-right {
|
||||
right: 0;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.openwebrx-panel
|
||||
{
|
||||
transform: perspective( 600px ) rotateX( 90deg );
|
||||
visibility: hidden;
|
||||
background-color: #575757;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
position: fixed;
|
||||
font-size: 10pt;
|
||||
border-radius: 15px;
|
||||
-moz-border-radius: 15px;
|
||||
margin: 5.9px;
|
||||
}
|
||||
|
||||
.openwebrx-panel a
|
||||
@ -439,26 +435,18 @@ input[type=range]:focus::-ms-fill-upper
|
||||
color: #FFFF50;
|
||||
}
|
||||
|
||||
.openwebrx-button:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.openwebrx-demodulator-button
|
||||
{
|
||||
width: 38px;
|
||||
height: 19px;
|
||||
font-size: 12pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.openwebrx-dial-button svg {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.openwebrx-dial-button #ph_dial {
|
||||
fill: #888;
|
||||
}
|
||||
|
||||
.openwebrx-dial-button.available #ph_dial {
|
||||
fill: #FFF;
|
||||
flex: 1;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.openwebrx-square-button img
|
||||
@ -554,7 +542,7 @@ img.openwebrx-mirror-img
|
||||
|
||||
#openwebrx-panel-status
|
||||
{
|
||||
margin: 0px;
|
||||
margin: 0 0 0 5.9px;
|
||||
padding: 0px;
|
||||
background-color:rgba(0, 0, 0, 0);
|
||||
}
|
||||
@ -615,6 +603,11 @@ img.openwebrx-mirror-img
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.openwebrx-panel-flex-line {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.openwebrx-panel-line:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
@ -725,6 +718,7 @@ img.openwebrx-mirror-img
|
||||
width: 173px;
|
||||
height: 27px;
|
||||
padding-left:3px;
|
||||
flex: 4;
|
||||
}
|
||||
|
||||
#openwebrx-sdr-profiles-listbox {
|
||||
@ -1021,7 +1015,8 @@ img.openwebrx-mirror-img
|
||||
|
||||
.openwebrx-dialog label {
|
||||
display: inline-block;
|
||||
flex: 1 0 20px;
|
||||
flex-grow: 0;
|
||||
width: 70px;
|
||||
padding-right: 20px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
@ -1029,7 +1024,7 @@ img.openwebrx-mirror-img
|
||||
|
||||
.openwebrx-dialog .form-field input,
|
||||
.openwebrx-dialog .form-field select {
|
||||
flex: 2 0 20px;
|
||||
flex-grow: 1;
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 459 B |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 2.7 KiB |
@ -4,9 +4,7 @@
|
||||
<div id="webrx-top-bar" class="webrx-top-bar-parts">
|
||||
<a href="https://sdr.hu/openwebrx" target="_blank"><img src="static/gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a>
|
||||
<a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="static/gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a>
|
||||
<div id="webrx-rx-avatar-background">
|
||||
<img id="webrx-rx-avatar" class="openwebrx-photo-trigger" src="static/gfx/openwebrx-avatar.png"/>
|
||||
</div>
|
||||
<img id="webrx-rx-avatar" class="openwebrx-photo-trigger" src="static/gfx/openwebrx-avatar.png"/>
|
||||
<div id="webrx-rx-texts">
|
||||
<div id="webrx-rx-title" class="openwebrx-photo-trigger"></div>
|
||||
<div id="webrx-rx-desc" class="openwebrx-photo-trigger"></div>
|
||||
|
@ -27,6 +27,10 @@
|
||||
<script src="static/openwebrx.js"></script>
|
||||
<script src="static/lib/jquery-3.2.1.min.js"></script>
|
||||
<script src="static/lib/jquery.nanoscroller.js"></script>
|
||||
<script src="static/lib/BookmarkBar.js"></script>
|
||||
<script src="static/lib/AudioEngine.js"></script>
|
||||
<script src="static/lib/ProgressBar.js"></script>
|
||||
<script src="static/lib/Measurement.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="static/lib/nanoscroller.css" />
|
||||
<link rel="stylesheet" type="text/css" href="static/css/openwebrx.css" />
|
||||
<meta charset="utf-8">
|
||||
@ -34,26 +38,107 @@
|
||||
<body onload="openwebrx_init();">
|
||||
<div id="webrx-page-container">
|
||||
${header}
|
||||
<div id="webrx-main-container">
|
||||
<div id="openwebrx-frequency-container">
|
||||
<div id="openwebrx-bookmarks-container"></div>
|
||||
<div id="openwebrx-scale-container">
|
||||
<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
|
||||
<div id="openwebrx-frequency-container">
|
||||
<div id="openwebrx-bookmarks-container"></div>
|
||||
<div id="openwebrx-scale-container">
|
||||
<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div id="openwebrx-mathbox-container"> </div>
|
||||
<div id="webrx-canvas-container">
|
||||
<!-- add canvas here by javascript -->
|
||||
</div>
|
||||
<div id="openwebrx-panels-container">
|
||||
<div id="openwebrx-panels-container-left">
|
||||
<div class="openwebrx-panel" data-panel-name="client-under-devel" style="width: 245px; background-color: Red;">
|
||||
<span style="font-size: 15pt; font-weight: bold;">Under construction</span>
|
||||
<br />We're working on the code right now, so the application might fail.
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-digimodes" style="display: none; width: 619px;" data-panel-name="digimodes">
|
||||
<div id="openwebrx-digimode-canvas-container">
|
||||
<div id="openwebrx-digimode-select-channel"></div>
|
||||
</div>
|
||||
<div id="openwebrx-digimode-content-container">
|
||||
<div class="gradient"></div>
|
||||
<div id="openwebrx-digimode-content">
|
||||
<span id="openwebrx-cursor-blink"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="openwebrx-panel" id="openwebrx-panel-wsjt-message" style="display: none; width: 619px;" data-panel-name="wsjt-message">
|
||||
<thead><tr>
|
||||
<th>UTC</th>
|
||||
<th class="decimal">dB</th>
|
||||
<th class="decimal">DT</th>
|
||||
<th class="decimal freq">Freq</th>
|
||||
<th class="message">Message</th>
|
||||
</tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<table class="openwebrx-panel" id="openwebrx-panel-packet-message" style="display: none; width: 619px;" data-panel-name="aprs-message">
|
||||
<thead><tr>
|
||||
<th>UTC</th>
|
||||
<th class="callsign">Callsign</th>
|
||||
<th class="coord">Coord</th>
|
||||
<th class="message">Comment</th>
|
||||
</tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-ysf" style="display: none;" data-panel-name="metadata-ysf">
|
||||
<div class="openwebrx-meta-frame">
|
||||
<div class="openwebrx-meta-slot">
|
||||
<div class="openwebrx-ysf-mode openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-meta-user-image"></div>
|
||||
<div class="openwebrx-ysf-source openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-ysf-up openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-ysf-down openwebrx-meta-autoclear"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-dmr" style="display: none;" data-panel-name="metadata-dmr">
|
||||
<div class="openwebrx-meta-frame">
|
||||
<div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel">
|
||||
<div class="openwebrx-dmr-slot">Timeslot 1</div>
|
||||
<div class="openwebrx-meta-user-image"></div>
|
||||
<div class="openwebrx-dmr-id openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-dmr-name openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-dmr-target openwebrx-meta-autoclear"></div>
|
||||
</div>
|
||||
<div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel">
|
||||
<div class="openwebrx-dmr-slot">Timeslot 2</div>
|
||||
<div class="openwebrx-meta-user-image"></div>
|
||||
<div class="openwebrx-dmr-id openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-dmr-name openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-dmr-target openwebrx-meta-autoclear"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" style="width: 619px;">
|
||||
<div class="openwebrx-panel-inner nano" id="openwebrx-log-scroll">
|
||||
<div class="nano-content">
|
||||
<div id="openwebrx-client-log-title">OpenWebRX client log</strong></div>
|
||||
<span id="openwebrx-client-1">Author: </span><a href="http://blog.sdr.hu/about" target="_blank">András Retzler, HA7ILM</a><br />You can support OpenWebRX development via <a href="http://blog.sdr.hu/support" target="_blank">PayPal!</a><br/>
|
||||
<div id="openwebrx-debugdiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" style="width: 615px;" data-panel-transparent="true">
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="openwebrx-mathbox-container"> </div>
|
||||
<div id="webrx-canvas-container">
|
||||
<div id="openwebrx-phantom-canvas"></div>
|
||||
<!-- add canvas here by javascript -->
|
||||
</div>
|
||||
<div id="openwebrx-panels-container">
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="259,115">
|
||||
<div id="openwebrx-panels-container-right">
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" style="width: 259px;">
|
||||
<div class="openwebrx-panel-line frequencies-container">
|
||||
<div class="frequencies">
|
||||
<div id="webrx-actual-freq">---.--- MHz</div>
|
||||
<div id="webrx-mouse-freq">---.--- MHz</div>
|
||||
</div>
|
||||
<div class="openwebrx-button openwebrx-square-button openwebrx-bookmark-button" style="display:none;">
|
||||
<div class="openwebrx-button openwebrx-square-button openwebrx-bookmark-button" style="display:none;" title="Add bookmark...">
|
||||
<img src="static/gfx/openwebrx-bookmark.png">
|
||||
</div>
|
||||
</div>
|
||||
@ -61,31 +146,33 @@
|
||||
<select id="openwebrx-sdr-profiles-listbox" onchange="sdr_profile_changed();">
|
||||
</select>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div class="openwebrx-panel-line openwebrx-panel-flex-line">
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-nfm"
|
||||
onclick="demodulator_analog_replace('nfm');">FM</div>
|
||||
onclick="demodulator_analog_replace('nfm');">FM</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-am"
|
||||
onclick="demodulator_analog_replace('am');">AM</div>
|
||||
onclick="demodulator_analog_replace('am');">AM</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-lsb"
|
||||
onclick="demodulator_analog_replace('lsb');">LSB</div>
|
||||
onclick="demodulator_analog_replace('lsb');">LSB</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-usb"
|
||||
onclick="demodulator_analog_replace('usb');">USB</div>
|
||||
onclick="demodulator_analog_replace('usb');">USB</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-cw"
|
||||
onclick="demodulator_analog_replace('cw');">CW</div>
|
||||
onclick="demodulator_analog_replace('cw');">CW</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line openwebrx-panel-flex-line">
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dmr"
|
||||
style="display:none;" data-feature="digital_voice_digiham"
|
||||
onclick="demodulator_analog_replace('dmr');">DMR</div>
|
||||
onclick="demodulator_analog_replace('dmr');">DMR</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dstar"
|
||||
style="display:none;" data-feature="digital_voice_dsd"
|
||||
onclick="demodulator_analog_replace('dstar');">DStar</div>
|
||||
onclick="demodulator_analog_replace('dstar');">DStar</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-nxdn"
|
||||
style="display:none;" data-feature="digital_voice_dsd"
|
||||
onclick="demodulator_analog_replace('nxdn');">NXDN</div>
|
||||
onclick="demodulator_analog_replace('nxdn');">NXDN</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-ysf"
|
||||
style="display:none;" data-feature="digital_voice_digiham"
|
||||
onclick="demodulator_analog_replace('ysf');">YSF</div>
|
||||
onclick="demodulator_analog_replace('ysf');">YSF</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div class="openwebrx-panel-line openwebrx-panel-flex-line">
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dig" onclick="demodulator_digital_replace_last();">DIG</div>
|
||||
<select id="openwebrx-secondary-demod-listbox" onchange="secondary_demod_listbox_changed();">
|
||||
<option value="none"></option>
|
||||
@ -97,13 +184,6 @@
|
||||
<option value="ft4" data-feature="wsjt-x">FT4</option>
|
||||
<option value="packet" data-feature="packet">Packet</option>
|
||||
</select>
|
||||
<div id="openwebrx-secondary-demod-dial-button" class="openwebrx-button openwebrx-dial-button" onclick="dial_button_click();">
|
||||
<svg version="1.1" id="Layer_1" x="0px" y="0px" width="246px" height="246px" viewBox="0 0 246 246" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ph_dial_1_" transform="matrix(1, 0, 0, 1, -45.398312, -50.931698)">
|
||||
<path id="ph_dial" d="M238.875,190.125c3.853,7.148,34.267,4.219,50.242,2.145c0.891-5.977,1.508-12.043,1.508-18.27 c0-67.723-54.901-122.625-122.625-122.625c-67.723,0-122.625,54.902-122.625,122.625c0,67.723,54.902,122.625,122.625,122.625 c51.06,0,94.797-31.227,113.25-75.609c-13.969-9.668-41.625-18.891-41.625-18.891c-5.25,0-10.5-3-12.75-8.25 S233.625,180.375,238.875,190.125z M220.465,175.313c0,28.478-23.086,51.563-51.563,51.563c-28.478,0-51.563-23.086-51.563-51.563 c0-28.477,23.086-51.563,51.563-51.563C197.379,123.75,220.465,146.836,220.465,175.313z M185.25,64.125 c10.563,0,19.125,8.563,19.125,19.125s-8.563,19.125-19.125,19.125c-10.562,0-19.125-8.563-19.125-19.125 S174.688,64.125,185.25,64.125z M142.875,69C153.438,69,162,77.563,162,88.125s-8.563,19.125-19.125,19.125 c-10.562,0-19.125-8.563-19.125-19.125S132.313,69,142.875,69z M106.5,91.875c10.563,0,19.125,8.563,19.125,19.125 s-8.563,19.125-19.125,19.125c-10.562,0-19.125-8.562-19.125-19.125S95.938,91.875,106.5,91.875z M81.375,126.75 c10.563,0,19.125,8.563,19.125,19.125S91.938,165,81.375,165c-10.563,0-19.125-8.563-19.125-19.125S70.813,126.75,81.375,126.75z M58.125,188.625c0-10.559,8.563-19.125,19.125-19.125c10.563,0,19.125,8.566,19.125,19.125S87.813,207.75,77.25,207.75 C66.687,207.75,58.125,199.184,58.125,188.625z M75.75,229.875c0-10.559,8.563-19.125,19.125-19.125 c10.563,0,19.125,8.566,19.125,19.125S105.438,249,94.875,249C84.312,249,75.75,240.434,75.75,229.875z M126.375,276 c-10.563,0-19.125-8.566-19.125-19.125s8.563-19.125,19.125-19.125c10.563,0,19.125,8.566,19.125,19.125S136.938,276,126.375,276z M168,288c-10.563,0-19.125-8.566-19.125-19.125S157.438,249.75,168,249.75c10.563,0,19.125,8.566,19.125,19.125 S178.563,288,168,288z M210.375,276c-10.563,0-19.125-8.566-19.125-19.125s8.563-19.125,19.125-19.125 c10.563,0,19.125,8.566,19.125,19.125S220.938,276,210.375,276z M243.375,210.75c10.563,0,19.125,8.566,19.125,19.125 S253.938,249,243.375,249c-10.563,0-19.125-8.566-19.125-19.125S232.813,210.75,243.375,210.75z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div title="Mute on/off" id="openwebrx-mute-off" class="openwebrx-button" onclick="toggleMute();"><img src="static/gfx/openwebrx-speaker.png" class="openwebrx-sliderbtn-img" id="openwebrx-mute-img"></div>
|
||||
@ -131,90 +211,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,137">
|
||||
<div class="openwebrx-panel-inner nano" id="openwebrx-log-scroll">
|
||||
<div class="nano-content">
|
||||
<div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div>
|
||||
<span id="openwebrx-client-1">Author: </span><a href="http://blog.sdr.hu/about" target="_blank">András Retzler, HA7ILM</a><br />You can support OpenWebRX development via <a href="http://blog.sdr.hu/support" target="_blank">PayPal!</a><br/>
|
||||
<div id="openwebrx-debugdiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" data-panel-pos="left" data-panel-order="0" data-panel-size="615,50" data-panel-transparent="true">
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="left" data-panel-order="9" data-panel-size="245,55" style="background-color: Red;">
|
||||
<span style="font-size: 15pt; font-weight: bold;">Under construction</span>
|
||||
<br />We're working on the code right now, so the application might fail.
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-digimodes" data-panel-name="digimodes" data-panel-pos="left" data-panel-order="3" data-panel-size="619,210">
|
||||
<div id="openwebrx-digimode-canvas-container">
|
||||
<div id="openwebrx-digimode-select-channel"></div>
|
||||
</div>
|
||||
<div id="openwebrx-digimode-content-container">
|
||||
<div class="gradient"></div>
|
||||
<div id="openwebrx-digimode-content">
|
||||
<span id="openwebrx-cursor-blink"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="openwebrx-panel" id="openwebrx-panel-wsjt-message" data-panel-name="wsjt-message" data-panel-pos="left" data-panel-order="2" data-panel-size="619,200">
|
||||
<thead><tr>
|
||||
<th>UTC</th>
|
||||
<th class="decimal">dB</th>
|
||||
<th class="decimal">DT</th>
|
||||
<th class="decimal freq">Freq</th>
|
||||
<th class="message">Message</th>
|
||||
</tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<table class="openwebrx-panel" id="openwebrx-panel-packet-message" data-panel-name="aprs-message" data-panel-pos="left" data-panel-order="2" data-panel-size="619,200">
|
||||
<thead><tr>
|
||||
<th>UTC</th>
|
||||
<th class="callsign">Callsign</th>
|
||||
<th class="coord">Coord</th>
|
||||
<th class="message">Comment</th>
|
||||
</tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-ysf" data-panel-name="metadata-ysf" data-panel-pos="left" data-panel-order="2" data-panel-size="145,220">
|
||||
<div class="openwebrx-meta-frame">
|
||||
<div class="openwebrx-meta-slot">
|
||||
<div class="openwebrx-ysf-mode openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-meta-user-image"></div>
|
||||
<div class="openwebrx-ysf-source openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-ysf-up openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-ysf-down openwebrx-meta-autoclear"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-dmr" data-panel-name="metadata-dmr" data-panel-pos="left" data-panel-order="2" data-panel-size="300,220">
|
||||
<div class="openwebrx-meta-frame">
|
||||
<div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel">
|
||||
<div class="openwebrx-dmr-slot">Timeslot 1</div>
|
||||
<div class="openwebrx-meta-user-image"></div>
|
||||
<div class="openwebrx-dmr-id openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-dmr-name openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-dmr-target openwebrx-meta-autoclear"></div>
|
||||
</div>
|
||||
<div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel">
|
||||
<div class="openwebrx-dmr-slot">Timeslot 2</div>
|
||||
<div class="openwebrx-meta-user-image"></div>
|
||||
<div class="openwebrx-dmr-id openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-dmr-name openwebrx-meta-autoclear"></div>
|
||||
<div class="openwebrx-dmr-target openwebrx-meta-autoclear"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="openwebrx-big-grey" onclick="iosPlayButtonClick();">
|
||||
<div id="openwebrx-big-grey" onclick="playButtonClick();">
|
||||
<div id="openwebrx-play-button-text">
|
||||
<img id="openwebrx-play-button" src="static/gfx/openwebrx-play-button.png" />
|
||||
<br /><br />Start OpenWebRX
|
||||
|
@ -8,7 +8,7 @@ AprsMarker.prototype.draw = function() {
|
||||
if (!div || !overlay) return;
|
||||
|
||||
if (this.symbol) {
|
||||
var tableId = this.symbol.table == '/' ? 0 : 1;
|
||||
var tableId = this.symbol.table === '/' ? 0 : 1;
|
||||
div.style.background = 'url(/aprs-symbols/aprs-symbols-24-' + tableId + '@2x.png)';
|
||||
div.style['background-size'] = '384px 144px';
|
||||
div.style['background-position-x'] = -(this.symbol.index % 16) * 24 + 'px';
|
||||
@ -25,7 +25,7 @@ AprsMarker.prototype.draw = function() {
|
||||
div.style.transform = null;
|
||||
}
|
||||
|
||||
if (this.symbol.table != '/' && this.symbol.table != '\\') {
|
||||
if (this.symbol.table !== '/' && this.symbol.table !== '\\') {
|
||||
overlay.style.display = 'block';
|
||||
overlay.style['background-position-x'] = -(this.symbol.tableindex % 16) * 24 + 'px';
|
||||
overlay.style['background-position-y'] = -Math.floor(this.symbol.tableindex / 16) * 24 + 'px';
|
||||
|
230
htdocs/lib/AudioEngine.js
Normal file
230
htdocs/lib/AudioEngine.js
Normal file
@ -0,0 +1,230 @@
|
||||
// this controls if the new AudioWorklet API should be used if available.
|
||||
// the engine will still fall back to the ScriptProcessorNode if this is set to true but not available in the browser.
|
||||
var useAudioWorklets = true;
|
||||
|
||||
function AudioEngine(maxBufferLength, audioReporter) {
|
||||
this.audioReporter = audioReporter;
|
||||
this.initStats();
|
||||
this.resetStats();
|
||||
var ctx = window.AudioContext || window.webkitAudioContext;
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
this.audioContext = new ctx();
|
||||
this.allowed = this.audioContext.state === 'running';
|
||||
this.started = false;
|
||||
|
||||
this.audioCodec = new sdrjs.ImaAdpcm();
|
||||
this.compression = 'none';
|
||||
|
||||
this.setupResampling();
|
||||
this.resampler = new sdrjs.RationalResamplerFF(this.resamplingFactor, 1);
|
||||
|
||||
this.maxBufferSize = maxBufferLength * this.getSampleRate();
|
||||
}
|
||||
|
||||
AudioEngine.prototype.start = function(callback) {
|
||||
var me = this;
|
||||
if (me.resamplingFactor === 0) return; //if failed to find a valid resampling factor...
|
||||
if (me.started) {
|
||||
if (callback) callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
me.audioContext.resume().then(function(){
|
||||
me.allowed = me.audioContext.state === 'running';
|
||||
if (!me.allowed) {
|
||||
if (callback) callback(false);
|
||||
return;
|
||||
}
|
||||
me.started = true;
|
||||
|
||||
me.gainNode = me.audioContext.createGain();
|
||||
me.gainNode.connect(me.audioContext.destination);
|
||||
|
||||
if (useAudioWorklets && me.audioContext.audioWorklet) {
|
||||
me.audioContext.audioWorklet.addModule('static/lib/AudioProcessor.js').then(function(){
|
||||
me.audioNode = new AudioWorkletNode(me.audioContext, 'openwebrx-audio-processor', {
|
||||
numberOfInputs: 0,
|
||||
numberOfOutputs: 1,
|
||||
outputChannelCount: [1],
|
||||
processorOptions: {
|
||||
maxBufferSize: me.maxBufferSize
|
||||
}
|
||||
});
|
||||
me.audioNode.connect(me.gainNode);
|
||||
me.audioNode.port.addEventListener('message', function(m){
|
||||
var json = JSON.parse(m.data);
|
||||
if (typeof(json.buffersize) !== 'undefined') {
|
||||
me.audioReporter({
|
||||
buffersize: json.buffersize
|
||||
});
|
||||
}
|
||||
if (typeof(json.samplesProcessed) !== 'undefined') {
|
||||
me.audioSamples.add(json.samplesProcessed);
|
||||
}
|
||||
});
|
||||
me.audioNode.port.start();
|
||||
if (callback) callback(true, 'AudioWorklet');
|
||||
});
|
||||
} else {
|
||||
me.audioBuffers = [];
|
||||
|
||||
if (!AudioBuffer.prototype.copyToChannel) { //Chrome 36 does not have it, Firefox does
|
||||
AudioBuffer.prototype.copyToChannel = function (input, channel) //input is Float32Array
|
||||
{
|
||||
var cd = this.getChannelData(channel);
|
||||
for (var i = 0; i < input.length; i++) cd[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
var bufferSize;
|
||||
if (me.audioContext.sampleRate < 44100 * 2)
|
||||
bufferSize = 4096;
|
||||
else if (me.audioContext.sampleRate >= 44100 * 2 && me.audioContext.sampleRate < 44100 * 4)
|
||||
bufferSize = 4096 * 2;
|
||||
else if (me.audioContext.sampleRate > 44100 * 4)
|
||||
bufferSize = 4096 * 4;
|
||||
|
||||
|
||||
function audio_onprocess(e) {
|
||||
var total = 0;
|
||||
var out = new Float32Array(bufferSize);
|
||||
while (me.audioBuffers.length) {
|
||||
var b = me.audioBuffers.shift();
|
||||
// not enough space to fit all data, so splice and put back in the queue
|
||||
if (total + b.length > bufferSize) {
|
||||
var spaceLeft = bufferSize - total;
|
||||
var tokeep = b.subarray(0, spaceLeft);
|
||||
out.set(tokeep, total);
|
||||
var tobuffer = b.subarray(spaceLeft, b.length);
|
||||
me.audioBuffers.unshift(tobuffer);
|
||||
total += spaceLeft;
|
||||
break;
|
||||
} else {
|
||||
out.set(b, total);
|
||||
total += b.length;
|
||||
}
|
||||
}
|
||||
|
||||
e.outputBuffer.copyToChannel(out, 0);
|
||||
me.audioSamples.add(total);
|
||||
|
||||
}
|
||||
|
||||
//on Chrome v36, createJavaScriptNode has been replaced by createScriptProcessor
|
||||
var method = 'createScriptProcessor';
|
||||
if (me.audioContext.createJavaScriptNode) {
|
||||
method = 'createJavaScriptNode';
|
||||
}
|
||||
me.audioNode = me.audioContext[method](bufferSize, 0, 1);
|
||||
me.audioNode.onaudioprocess = audio_onprocess;
|
||||
me.audioNode.connect(me.gainNode);
|
||||
if (callback) callback(true, 'ScriptProcessorNode');
|
||||
}
|
||||
|
||||
setInterval(me.reportStats.bind(me), 1000);
|
||||
});
|
||||
};
|
||||
|
||||
AudioEngine.prototype.isAllowed = function() {
|
||||
return this.allowed;
|
||||
};
|
||||
|
||||
AudioEngine.prototype.reportStats = function() {
|
||||
if (this.audioNode.port) {
|
||||
this.audioNode.port.postMessage(JSON.stringify({cmd:'getStats'}));
|
||||
} else {
|
||||
this.audioReporter({
|
||||
buffersize: this.getBuffersize()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AudioEngine.prototype.initStats = function() {
|
||||
var me = this;
|
||||
var buildReporter = function(key) {
|
||||
return function(v){
|
||||
var report = {};
|
||||
report[key] = v;
|
||||
me.audioReporter(report);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.audioBytes = new Measurement();
|
||||
this.audioBytes.report(10000, 1000, buildReporter('audioByteRate'));
|
||||
|
||||
this.audioSamples = new Measurement();
|
||||
this.audioSamples.report(10000, 1000, buildReporter('audioRate'));
|
||||
};
|
||||
|
||||
AudioEngine.prototype.resetStats = function() {
|
||||
this.audioBytes.reset();
|
||||
this.audioSamples.reset();
|
||||
};
|
||||
|
||||
AudioEngine.prototype.setupResampling = function() { //both at the server and the client
|
||||
var output_range_max = 12000;
|
||||
var output_range_min = 8000;
|
||||
var targetRate = this.audioContext.sampleRate;
|
||||
var i = 1;
|
||||
while (true) {
|
||||
var audio_server_output_rate = Math.floor(targetRate / i);
|
||||
if (audio_server_output_rate < output_range_min) {
|
||||
this.resamplingFactor = 0;
|
||||
this.outputRate = 0;
|
||||
divlog('Your audio card sampling rate (' + targetRate + ') is not supported.<br />Please change your operating system default settings in order to fix this.', 1);
|
||||
break;
|
||||
} else if (audio_server_output_rate >= output_range_min && audio_server_output_rate <= output_range_max) {
|
||||
this.resamplingFactor = i;
|
||||
this.outputRate = audio_server_output_rate;
|
||||
break; //okay, we're done
|
||||
}
|
||||
i++;
|
||||
}
|
||||
};
|
||||
|
||||
AudioEngine.prototype.getOutputRate = function() {
|
||||
return this.outputRate;
|
||||
};
|
||||
|
||||
AudioEngine.prototype.getSampleRate = function() {
|
||||
return this.audioContext.sampleRate;
|
||||
};
|
||||
|
||||
AudioEngine.prototype.pushAudio = function(data) {
|
||||
if (!this.audioNode) return;
|
||||
this.audioBytes.add(data.byteLength);
|
||||
var buffer;
|
||||
if (this.compression === "adpcm") {
|
||||
//resampling & ADPCM
|
||||
buffer = this.audioCodec.decode(new Uint8Array(data));
|
||||
} else {
|
||||
buffer = new Int16Array(data);
|
||||
}
|
||||
buffer = this.resampler.process(sdrjs.ConvertI16_F(buffer));
|
||||
if (this.audioNode.port) {
|
||||
// AudioWorklets supported
|
||||
this.audioNode.port.postMessage(buffer);
|
||||
} else {
|
||||
// silently drop excess samples
|
||||
if (this.getBuffersize() + buffer.length <= this.maxBufferSize) {
|
||||
this.audioBuffers.push(buffer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AudioEngine.prototype.setCompression = function(compression) {
|
||||
this.compression = compression;
|
||||
};
|
||||
|
||||
AudioEngine.prototype.setVolume = function(volume) {
|
||||
this.gainNode.gain.value = volume;
|
||||
};
|
||||
|
||||
AudioEngine.prototype.getBuffersize = function() {
|
||||
// only available when using ScriptProcessorNode
|
||||
if (!this.audioBuffers) return 0;
|
||||
return this.audioBuffers.map(function(b){ return b.length; }).reduce(function(a, b){ return a + b; }, 0);
|
||||
};
|
58
htdocs/lib/AudioProcessor.js
Normal file
58
htdocs/lib/AudioProcessor.js
Normal file
@ -0,0 +1,58 @@
|
||||
class OwrxAudioProcessor extends AudioWorkletProcessor {
|
||||
constructor(options){
|
||||
super(options);
|
||||
// initialize ringbuffer, make sure it aligns with the expected buffer size of 128
|
||||
this.bufferSize = Math.round(options.processorOptions.maxBufferSize / 128) * 128;
|
||||
this.audioBuffer = new Float32Array(this.bufferSize);
|
||||
this.inPos = 0;
|
||||
this.outPos = 0;
|
||||
this.samplesProcessed = 0;
|
||||
this.port.addEventListener('message', (m) => {
|
||||
if (typeof(m.data) === 'string') {
|
||||
const json = JSON.parse(m.data);
|
||||
if (json.cmd && json.cmd === 'getStats') {
|
||||
this.reportStats();
|
||||
}
|
||||
} else {
|
||||
// the ringbuffer size is aligned to the output buffer size, which means that the input buffers might
|
||||
// need to wrap around the end of the ringbuffer, back to the start.
|
||||
// it is better to have this processing here instead of in the time-critical process function.
|
||||
if (this.inPos + m.data.length <= this.bufferSize) {
|
||||
// we have enough space, so just copy data over.
|
||||
this.audioBuffer.set(m.data, this.inPos);
|
||||
} else {
|
||||
// we don't have enough space, so we need to split the data.
|
||||
const remaining = this.bufferSize - this.inPos;
|
||||
this.audioBuffer.set(m.data.subarray(0, remaining), this.inPos);
|
||||
this.audioBuffer.set(m.data.subarray(remaining));
|
||||
}
|
||||
this.inPos = (this.inPos + m.data.length) % this.bufferSize;
|
||||
}
|
||||
});
|
||||
this.port.addEventListener('messageerror', console.error);
|
||||
this.port.start();
|
||||
}
|
||||
process(inputs, outputs) {
|
||||
if (this.remaining() < 128) return true;
|
||||
outputs[0].forEach((output) => {
|
||||
output.set(this.audioBuffer.subarray(this.outPos, this.outPos + 128));
|
||||
});
|
||||
this.outPos = (this.outPos + 128) % this.bufferSize;
|
||||
this.samplesProcessed += 128;
|
||||
return true;
|
||||
}
|
||||
remaining() {
|
||||
const mod = (this.inPos - this.outPos) % this.bufferSize;
|
||||
if (mod >= 0) return mod;
|
||||
return mod + this.bufferSize;
|
||||
}
|
||||
reportStats() {
|
||||
this.port.postMessage(JSON.stringify({
|
||||
buffersize: this.remaining(),
|
||||
samplesProcessed: this.samplesProcessed
|
||||
}));
|
||||
this.samplesProcessed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('openwebrx-audio-processor', OwrxAudioProcessor);
|
177
htdocs/lib/BookmarkBar.js
Normal file
177
htdocs/lib/BookmarkBar.js
Normal file
@ -0,0 +1,177 @@
|
||||
function BookmarkBar() {
|
||||
var me = this;
|
||||
me.localBookmarks = new BookmarkLocalStorage();
|
||||
me.$container = $("#openwebrx-bookmarks-container");
|
||||
me.bookmarks = {};
|
||||
|
||||
me.$container.on('click', '.bookmark', function(e){
|
||||
var $bookmark = $(e.target).closest('.bookmark');
|
||||
me.$container.find('.bookmark').removeClass('selected');
|
||||
var b = $bookmark.data();
|
||||
if (!b || !b.frequency || (!b.modulation && !b.digital_modulation)) return;
|
||||
demodulator_set_offset_frequency(0, b.frequency - center_freq);
|
||||
if (b.modulation) {
|
||||
demodulator_analog_replace(b.modulation);
|
||||
} else if (b.digital_modulation) {
|
||||
demodulator_digital_replace(b.digital_modulation);
|
||||
}
|
||||
$bookmark.addClass('selected');
|
||||
});
|
||||
|
||||
me.$container.on('click', '.action[data-action=edit]', function(e){
|
||||
e.stopPropagation();
|
||||
var $bookmark = $(e.target).closest('.bookmark');
|
||||
me.showEditDialog($bookmark.data());
|
||||
});
|
||||
|
||||
me.$container.on('click', '.action[data-action=delete]', function(e){
|
||||
e.stopPropagation();
|
||||
var $bookmark = $(e.target).closest('.bookmark');
|
||||
me.localBookmarks.deleteBookmark($bookmark.data());
|
||||
me.loadLocalBookmarks();
|
||||
});
|
||||
|
||||
var $bookmarkButton = $('#openwebrx-panel-receiver').find('.openwebrx-bookmark-button');
|
||||
if (typeof(Storage) !== 'undefined') {
|
||||
$bookmarkButton.show();
|
||||
} else {
|
||||
$bookmarkButton.hide();
|
||||
}
|
||||
$bookmarkButton.click(function(){
|
||||
me.showEditDialog();
|
||||
});
|
||||
|
||||
me.$dialog = $("#openwebrx-dialog-bookmark");
|
||||
me.$dialog.find('.openwebrx-button[data-action=cancel]').click(function(){
|
||||
me.$dialog.hide();
|
||||
});
|
||||
me.$dialog.find('.openwebrx-button[data-action=submit]').click(function(){
|
||||
me.storeBookmark();
|
||||
});
|
||||
me.$dialog.find('form').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
me.storeBookmark();
|
||||
});
|
||||
}
|
||||
|
||||
BookmarkBar.prototype.position = function(){
|
||||
var range = get_visible_freq_range();
|
||||
$('#openwebrx-bookmarks-container').find('.bookmark').each(function(){
|
||||
$(this).css('left', scale_px_from_freq($(this).data('frequency'), range));
|
||||
});
|
||||
};
|
||||
|
||||
BookmarkBar.prototype.loadLocalBookmarks = function(){
|
||||
var bwh = bandwidth / 2;
|
||||
var start = center_freq - bwh;
|
||||
var end = center_freq + bwh;
|
||||
var bookmarks = this.localBookmarks.getBookmarks().filter(function(b){
|
||||
return b.frequency >= start && b.frequency <= end;
|
||||
});
|
||||
this.replace_bookmarks(bookmarks, 'local', true);
|
||||
};
|
||||
|
||||
BookmarkBar.prototype.replace_bookmarks = function(bookmarks, source, editable) {
|
||||
editable = !!editable;
|
||||
bookmarks = bookmarks.map(function(b){
|
||||
b.source = source;
|
||||
b.editable = editable;
|
||||
return b;
|
||||
});
|
||||
this.bookmarks[source] = bookmarks;
|
||||
this.render();
|
||||
};
|
||||
|
||||
BookmarkBar.prototype.render = function(){
|
||||
var bookmarks = Object.values(this.bookmarks).reduce(function(l, v){ return l.concat(v); });
|
||||
bookmarks = bookmarks.sort(function(a, b){ return a.frequency - b.frequency; });
|
||||
var elements = bookmarks.map(function(b){
|
||||
var $bookmark = $(
|
||||
'<div class="bookmark" data-source="' + b.source + '"' + (b.editable?' editable="editable"':'') + '>' +
|
||||
'<div class="bookmark-actions">' +
|
||||
'<div class="openwebrx-button action" data-action="edit"><img src="static/gfx/openwebrx-edit.png"></div>' +
|
||||
'<div class="openwebrx-button action" data-action="delete"><img src="static/gfx/openwebrx-trashcan.png"></div>' +
|
||||
'</div>' +
|
||||
'<div class="bookmark-content">' + b.name + '</div>' +
|
||||
'</div>'
|
||||
);
|
||||
$bookmark.data(b);
|
||||
return $bookmark;
|
||||
});
|
||||
this.$container.find('.bookmark').remove();
|
||||
this.$container.append(elements);
|
||||
this.position();
|
||||
};
|
||||
|
||||
BookmarkBar.prototype.showEditDialog = function(bookmark) {
|
||||
var $form = this.$dialog.find("form");
|
||||
if (!bookmark) {
|
||||
bookmark = {
|
||||
name: "",
|
||||
frequency: center_freq + demodulators[0].offset_frequency,
|
||||
modulation: demodulators[0].subtype
|
||||
}
|
||||
}
|
||||
['name', 'frequency', 'modulation'].forEach(function(key){
|
||||
$form.find('#' + key).val(bookmark[key]);
|
||||
});
|
||||
this.$dialog.data('id', bookmark.id);
|
||||
this.$dialog.show();
|
||||
this.$dialog.find('#name').focus();
|
||||
};
|
||||
|
||||
BookmarkBar.prototype.storeBookmark = function() {
|
||||
var me = this;
|
||||
var bookmark = {};
|
||||
var valid = true;
|
||||
['name', 'frequency', 'modulation'].forEach(function(key){
|
||||
var $input = me.$dialog.find('#' + key);
|
||||
valid = valid && $input[0].checkValidity();
|
||||
bookmark[key] = $input.val();
|
||||
});
|
||||
if (!valid) {
|
||||
me.$dialog.find("form :submit").click();
|
||||
return;
|
||||
}
|
||||
bookmark.frequency = Number(bookmark.frequency);
|
||||
|
||||
var bookmarks = me.localBookmarks.getBookmarks();
|
||||
|
||||
bookmark.id = me.$dialog.data('id');
|
||||
if (!bookmark.id) {
|
||||
if (bookmarks.length) {
|
||||
bookmark.id = 1 + Math.max.apply(Math, bookmarks.map(function(b){ return b.id || 0; }));
|
||||
} else {
|
||||
bookmark.id = 1;
|
||||
}
|
||||
}
|
||||
|
||||
bookmarks = bookmarks.filter(function(b) { return b.id !== bookmark.id; });
|
||||
bookmarks.push(bookmark);
|
||||
|
||||
me.localBookmarks.setBookmarks(bookmarks);
|
||||
me.loadLocalBookmarks();
|
||||
me.$dialog.hide();
|
||||
};
|
||||
|
||||
BookmarkLocalStorage = function(){
|
||||
};
|
||||
|
||||
BookmarkLocalStorage.prototype.getBookmarks = function(){
|
||||
return JSON.parse(window.localStorage.getItem("bookmarks")) || [];
|
||||
};
|
||||
|
||||
BookmarkLocalStorage.prototype.setBookmarks = function(bookmarks){
|
||||
window.localStorage.setItem("bookmarks", JSON.stringify(bookmarks));
|
||||
};
|
||||
|
||||
BookmarkLocalStorage.prototype.deleteBookmark = function(data) {
|
||||
if (data.id) data = data.id;
|
||||
var bookmarks = this.getBookmarks();
|
||||
bookmarks = bookmarks.filter(function(b) { return b.id !== data; });
|
||||
this.setBookmarks(bookmarks);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
62
htdocs/lib/Measurement.js
Normal file
62
htdocs/lib/Measurement.js
Normal file
@ -0,0 +1,62 @@
|
||||
function Measurement() {
|
||||
this.reset();
|
||||
};
|
||||
|
||||
Measurement.prototype.add = function(v) {
|
||||
this.value += v;
|
||||
};
|
||||
|
||||
Measurement.prototype.getValue = function() {
|
||||
return this.value;
|
||||
};
|
||||
|
||||
Measurement.prototype.getElapsed = function() {
|
||||
return new Date() - this.start;
|
||||
};
|
||||
|
||||
Measurement.prototype.getRate = function() {
|
||||
return this.getValue() / this.getElapsed();
|
||||
};
|
||||
|
||||
Measurement.prototype.reset = function() {
|
||||
this.value = 0;
|
||||
this.start = new Date();
|
||||
};
|
||||
|
||||
Measurement.prototype.report = function(range, interval, callback) {
|
||||
return new Reporter(this, range, interval, callback);
|
||||
}
|
||||
|
||||
function Reporter(measurement, range, interval, callback) {
|
||||
this.measurement = measurement;
|
||||
this.range = range;
|
||||
this.samples = [];
|
||||
this.callback = callback;
|
||||
this.interval = setInterval(this.report.bind(this), interval);
|
||||
};
|
||||
|
||||
Reporter.prototype.sample = function(){
|
||||
this.samples.push({
|
||||
timestamp: new Date(),
|
||||
value: this.measurement.getValue()
|
||||
});
|
||||
};
|
||||
|
||||
Reporter.prototype.report = function(){
|
||||
this.sample();
|
||||
var now = new Date();
|
||||
var minDate = now.getTime() - this.range;
|
||||
this.samples = this.samples.filter(function(s) {
|
||||
return s.timestamp.getTime() > minDate;
|
||||
});
|
||||
this.samples.sort(function(a, b) {
|
||||
return a.timestamp - b.timestamp;
|
||||
});
|
||||
var oldest = this.samples[0];
|
||||
var newest = this.samples[this.samples.length -1];
|
||||
var elapsed = newest.timestamp - oldest.timestamp;
|
||||
if (elapsed <= 0) return;
|
||||
var accumulated = newest.value - oldest.value;
|
||||
// we want rate per second, but our time is in milliseconds... compensate by 1000
|
||||
this.callback(accumulated * 1000 / elapsed);
|
||||
};
|
113
htdocs/lib/ProgressBar.js
Normal file
113
htdocs/lib/ProgressBar.js
Normal file
@ -0,0 +1,113 @@
|
||||
ProgressBar = function(el) {
|
||||
this.$el = $(el);
|
||||
this.$innerText = this.$el.find('.openwebrx-progressbar-text');
|
||||
this.$innerBar = this.$el.find('.openwebrx-progressbar-bar');
|
||||
this.$innerBar.css('width', '0%');
|
||||
};
|
||||
|
||||
ProgressBar.prototype.set = function(val, text, over) {
|
||||
this.setValue(val);
|
||||
this.setText(text);
|
||||
this.setOver(over);
|
||||
};
|
||||
|
||||
ProgressBar.prototype.setValue = function(val) {
|
||||
if (val < 0) val = 0;
|
||||
if (val > 1) val = 1;
|
||||
this.$innerBar.stop().animate({width: val * 100 + '%'}, 700);
|
||||
};
|
||||
|
||||
ProgressBar.prototype.setText = function(text) {
|
||||
this.$innerText.html(text);
|
||||
};
|
||||
|
||||
ProgressBar.prototype.setOver = function(over) {
|
||||
this.$innerBar.css('backgroundColor', (over) ? "#ff6262" : "#00aba6");
|
||||
};
|
||||
|
||||
AudioBufferProgressBar = function(el, sampleRate) {
|
||||
ProgressBar.call(this, el);
|
||||
this.sampleRate = sampleRate;
|
||||
};
|
||||
|
||||
AudioBufferProgressBar.prototype = new ProgressBar();
|
||||
|
||||
AudioBufferProgressBar.prototype.setBuffersize = function(buffersize) {
|
||||
var audio_buffer_value = buffersize / this.sampleRate;
|
||||
var overrun = audio_buffer_value > audio_buffer_maximal_length_sec;
|
||||
var underrun = audio_buffer_value === 0;
|
||||
var text = "buffer";
|
||||
if (overrun) {
|
||||
text = "overrun";
|
||||
}
|
||||
if (underrun) {
|
||||
text = "underrun";
|
||||
}
|
||||
this.set(audio_buffer_value, "Audio " + text + " [" + (audio_buffer_value).toFixed(1) + " s]", overrun || underrun);
|
||||
};
|
||||
|
||||
|
||||
NetworkSpeedProgressBar = function(el) {
|
||||
ProgressBar.call(this, el);
|
||||
};
|
||||
|
||||
NetworkSpeedProgressBar.prototype = new ProgressBar();
|
||||
|
||||
NetworkSpeedProgressBar.prototype.setSpeed = function(speed) {
|
||||
var speedInKilobits = speed * 8 / 1000;
|
||||
this.set(speedInKilobits / 2000, "Network usage [" + speedInKilobits.toFixed(1) + " kbps]", false);
|
||||
};
|
||||
|
||||
AudioSpeedProgressBar = function(el) {
|
||||
ProgressBar.call(this, el);
|
||||
};
|
||||
|
||||
AudioSpeedProgressBar.prototype = new ProgressBar();
|
||||
|
||||
AudioSpeedProgressBar.prototype.setSpeed = function(speed) {
|
||||
this.set(speed / 500000, "Audio stream [" + (speed / 1000).toFixed(0) + " kbps]", false);
|
||||
};
|
||||
|
||||
AudioOutputProgressBar = function(el, sampleRate) {
|
||||
ProgressBar.call(this, el);
|
||||
this.maxRate = sampleRate * 1.25;
|
||||
this.minRate = sampleRate * .25;
|
||||
};
|
||||
|
||||
AudioOutputProgressBar.prototype = new ProgressBar();
|
||||
|
||||
AudioOutputProgressBar.prototype.setAudioRate = function(audioRate) {
|
||||
this.set(audioRate / this.maxRate, "Audio output [" + (audioRate / 1000).toFixed(1) + " ksps]", audioRate > this.maxRate || audioRate < this.minRate);
|
||||
};
|
||||
|
||||
ClientsProgressBar = function(el) {
|
||||
ProgressBar.call(this, el);
|
||||
this.clients = 0;
|
||||
this.maxClients = 0;
|
||||
};
|
||||
|
||||
ClientsProgressBar.prototype = new ProgressBar();
|
||||
|
||||
ClientsProgressBar.prototype.setClients = function(clients) {
|
||||
this.clients = clients;
|
||||
this.render();
|
||||
};
|
||||
|
||||
ClientsProgressBar.prototype.setMaxClients = function(maxClients) {
|
||||
this.maxClients = maxClients;
|
||||
this.render();
|
||||
};
|
||||
|
||||
ClientsProgressBar.prototype.render = function() {
|
||||
this.set(this.clients / this.maxClients, "Clients [" + this.clients + "]", this.clients > this.maxClients * 0.85);
|
||||
};
|
||||
|
||||
CpuProgressBar = function(el) {
|
||||
ProgressBar.call(this, el);
|
||||
};
|
||||
|
||||
CpuProgressBar.prototype = new ProgressBar();
|
||||
|
||||
CpuProgressBar.prototype.setUsage = function(usage) {
|
||||
this.set(usage, "Server CPU [" + Math.round(usage * 100) + "%]", usage > .85);
|
||||
};
|
4315
htdocs/openwebrx.js
4315
htdocs/openwebrx.js
File diff suppressed because it is too large
Load Diff
@ -60,3 +60,4 @@ if __name__ == "__main__":
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
WebSocketConnection.closeAll()
|
||||
Services.stop()
|
||||
|
@ -5,6 +5,7 @@ from owrx.version import openwebrx_version
|
||||
from owrx.bands import Bandplan
|
||||
from owrx.bookmarks import Bookmarks
|
||||
from owrx.map import Map
|
||||
from owrx.locator import Locator
|
||||
from multiprocessing import Queue
|
||||
import json
|
||||
import threading
|
||||
@ -64,7 +65,6 @@ class OpenWebRxReceiverClient(Client):
|
||||
"fft_compression",
|
||||
"max_clients",
|
||||
"start_mod",
|
||||
"client_audio_buffer_size",
|
||||
"start_freq",
|
||||
"center_freq",
|
||||
"mathbox_waterfall_colors",
|
||||
@ -89,13 +89,13 @@ class OpenWebRxReceiverClient(Client):
|
||||
receiver_keys = [
|
||||
"receiver_name",
|
||||
"receiver_location",
|
||||
"receiver_qra",
|
||||
"receiver_asl",
|
||||
"receiver_gps",
|
||||
"photo_title",
|
||||
"photo_desc",
|
||||
]
|
||||
receiver_details = dict((key, pm.getPropertyValue(key)) for key in receiver_keys)
|
||||
receiver_details["locator"] = Locator.fromCoordinates(receiver_details["receiver_gps"])
|
||||
self.write_receiver_details(receiver_details)
|
||||
|
||||
profiles = [
|
||||
@ -141,6 +141,11 @@ class OpenWebRxReceiverClient(Client):
|
||||
|
||||
def setSdr(self, id=None):
|
||||
next = SdrService.getSource(id)
|
||||
|
||||
if next is None:
|
||||
self.handleSdrFailure("sdr device failed")
|
||||
return
|
||||
|
||||
if next == self.sdr:
|
||||
return
|
||||
|
||||
@ -152,6 +157,8 @@ class OpenWebRxReceiverClient(Client):
|
||||
|
||||
self.sdr = next
|
||||
|
||||
self.startDsp()
|
||||
|
||||
# send initial config
|
||||
configProps = (
|
||||
self.sdr.getProps()
|
||||
@ -163,6 +170,8 @@ class OpenWebRxReceiverClient(Client):
|
||||
config = dict((key, configProps[key]) for key in OpenWebRxReceiverClient.config_keys)
|
||||
# TODO mathematical properties? hmmmm
|
||||
config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"]
|
||||
# TODO this is a hack that only works because setting the profile always causes plenty of config change
|
||||
config["profile_id"] = self.sdr.getId() + "|" + self.sdr.getProfileId()
|
||||
self.write_config(config)
|
||||
|
||||
cf = configProps["center_freq"]
|
||||
@ -177,6 +186,9 @@ class OpenWebRxReceiverClient(Client):
|
||||
|
||||
self.sdr.addSpectrumClient(self)
|
||||
|
||||
def handleSdrFailure(self, message):
|
||||
self.write_sdr_error(message)
|
||||
|
||||
def startDsp(self):
|
||||
if self.dsp is None:
|
||||
self.dsp = DspManager(self, self.sdr)
|
||||
@ -231,7 +243,8 @@ class OpenWebRxReceiverClient(Client):
|
||||
self.send(bytes([0x03]) + data)
|
||||
|
||||
def write_secondary_demod(self, data):
|
||||
self.send(bytes([0x04]) + data)
|
||||
message = data.decode('ascii')
|
||||
self.send({"type": "secondary_demod", "value": message})
|
||||
|
||||
def write_secondary_dsp_config(self, cfg):
|
||||
self.send({"type": "secondary_config", "value": cfg})
|
||||
@ -263,6 +276,9 @@ class OpenWebRxReceiverClient(Client):
|
||||
def write_aprs_data(self, data):
|
||||
self.send({"type": "aprs_data", "value": data})
|
||||
|
||||
def write_sdr_error(self, message):
|
||||
self.send({"type": "sdr_error", "value": message})
|
||||
|
||||
|
||||
class MapConnection(Client):
|
||||
def __init__(self, conn):
|
||||
|
@ -101,7 +101,7 @@ class AprsSymbolsController(AssetsController):
|
||||
|
||||
class TemplateController(Controller):
|
||||
def render_template(self, file, **vars):
|
||||
f = open("htdocs/" + file, "r")
|
||||
f = open("htdocs/" + file, "r", encoding="utf-8")
|
||||
template = Template(f.read())
|
||||
f.close()
|
||||
|
||||
@ -152,4 +152,4 @@ class WebSocketController(Controller):
|
||||
def handle_request(self):
|
||||
conn = WebSocketConnection(self.handler, WebSocketMessageHandler())
|
||||
# enter read loop
|
||||
conn.read_loop()
|
||||
conn.handle()
|
||||
|
@ -8,6 +8,7 @@ from owrx.wsjt import WsjtParser
|
||||
from owrx.aprs import AprsParser
|
||||
from owrx.config import PropertyManager
|
||||
from owrx.source import Resampler
|
||||
from owrx.feature import FeatureDetector
|
||||
|
||||
import logging
|
||||
|
||||
@ -119,11 +120,14 @@ class ServiceScheduler(object):
|
||||
if time is not None:
|
||||
delta = time - datetime.utcnow()
|
||||
seconds = delta.total_seconds()
|
||||
if self.selectionTimer:
|
||||
self.selectionTimer.cancel()
|
||||
self.cancelTimer()
|
||||
self.selectionTimer = threading.Timer(seconds, self.selectProfile)
|
||||
self.selectionTimer.start()
|
||||
|
||||
def cancelTimer(self):
|
||||
if self.selectionTimer:
|
||||
self.selectionTimer.cancel()
|
||||
|
||||
def isActive(self):
|
||||
return self.active
|
||||
|
||||
@ -133,6 +137,9 @@ class ServiceScheduler(object):
|
||||
def onSdrUnavailable(self):
|
||||
self.scheduleSelection()
|
||||
|
||||
def onSdrFailed(self):
|
||||
self.cancelTimer()
|
||||
|
||||
def selectProfile(self):
|
||||
self.active = False
|
||||
if self.source.hasActiveClients():
|
||||
@ -183,8 +190,29 @@ class ServiceHandler(object):
|
||||
logger.debug("sdr source becoming unavailable; stopping services.")
|
||||
self.stopServices()
|
||||
|
||||
def onSdrFailed(self):
|
||||
logger.debug("sdr source failed; stopping services.")
|
||||
self.stopServices()
|
||||
|
||||
def isSupported(self, mode):
|
||||
return mode in PropertyManager.getSharedInstance()["services_decoders"]
|
||||
# TODO this should be in a more central place (the frontend also needs this)
|
||||
requirements = {
|
||||
'ft8': 'wsjt-x',
|
||||
'ft4': 'wsjt-x',
|
||||
'jt65': 'wsjt-x',
|
||||
'jt9': 'wsjt-x',
|
||||
'wspr': 'wsjt-x',
|
||||
'packet': 'packet',
|
||||
}
|
||||
fd = FeatureDetector()
|
||||
|
||||
# this looks overly complicated... but i'd like modes with no requirements to be always available without
|
||||
# being listed in the hash above
|
||||
unavailable = [mode for mode, req in requirements.items() if not fd.is_available(req)]
|
||||
configured = PropertyManager.getSharedInstance()["services_decoders"]
|
||||
available = [mode for mode in configured if mode not in unavailable]
|
||||
|
||||
return mode in available
|
||||
|
||||
def stopServices(self):
|
||||
with self.lock:
|
||||
@ -238,23 +266,28 @@ class ServiceHandler(object):
|
||||
with self.lock:
|
||||
self.services = []
|
||||
|
||||
for group in self.optimizeResampling(dials, sr):
|
||||
frequencies = sorted([f["frequency"] for f in group])
|
||||
min = frequencies[0]
|
||||
max = frequencies[-1]
|
||||
cf = (min + max) / 2
|
||||
bw = max - min
|
||||
logger.debug("group center frequency: {0}, bandwidth: {1}".format(cf, bw))
|
||||
resampler_props = PropertyManager()
|
||||
resampler_props["center_freq"] = cf
|
||||
# TODO the + 24000 is a temporary fix since the resampling optimizer does not account for required bandwidths
|
||||
resampler_props["samp_rate"] = bw + 24000
|
||||
resampler = Resampler(resampler_props, self.getAvailablePort(), self.source)
|
||||
resampler.start()
|
||||
self.services.append(resampler)
|
||||
groups = self.optimizeResampling(dials, sr)
|
||||
if groups is None:
|
||||
for dial in dials:
|
||||
self.services.append(self.setupService(dial["mode"], dial["frequency"], self.source))
|
||||
else:
|
||||
for group in groups:
|
||||
frequencies = sorted([f["frequency"] for f in group])
|
||||
min = frequencies[0]
|
||||
max = frequencies[-1]
|
||||
cf = (min + max) / 2
|
||||
bw = max - min
|
||||
logger.debug("group center frequency: {0}, bandwidth: {1}".format(cf, bw))
|
||||
resampler_props = PropertyManager()
|
||||
resampler_props["center_freq"] = cf
|
||||
# TODO the + 24000 is a temporary fix since the resampling optimizer does not account for required bandwidths
|
||||
resampler_props["samp_rate"] = bw + 24000
|
||||
resampler = Resampler(resampler_props, self.getAvailablePort(), self.source)
|
||||
resampler.start()
|
||||
self.services.append(resampler)
|
||||
|
||||
for dial in group:
|
||||
self.services.append(self.setupService(dial["mode"], dial["frequency"], resampler))
|
||||
for dial in group:
|
||||
self.services.append(self.setupService(dial["mode"], dial["frequency"], resampler))
|
||||
|
||||
def optimizeResampling(self, freqs, bandwidth):
|
||||
freqs = sorted(freqs, key=lambda f: f["frequency"])
|
||||
@ -285,14 +318,17 @@ class ServiceHandler(object):
|
||||
return {"num_splits": num_splits, "total_bandwidth": total_bandwidth, "groups": groups}
|
||||
|
||||
usages = [calculate_usage(i) for i in range(0, len(freqs))]
|
||||
# this is simulating no resampling. i haven't seen this as the best result yet
|
||||
# another possible outcome might be that it's best not to resample at all. this is a special case.
|
||||
usages += [{"num_splits": None, "total_bandwidth": bandwidth * len(freqs), "groups": [freqs]}]
|
||||
results = sorted(usages, key=lambda f: f["total_bandwidth"])
|
||||
|
||||
for r in results:
|
||||
logger.debug("splits: {0}, total: {1}".format(r["num_splits"], r["total_bandwidth"]))
|
||||
|
||||
return results[0]["groups"]
|
||||
best = results[0]
|
||||
if best["num_splits"] is None:
|
||||
return None
|
||||
return best["groups"]
|
||||
|
||||
def setupService(self, mode, frequency, source):
|
||||
logger.debug("setting up service {0} on frequency {1}".format(mode, frequency))
|
||||
@ -333,12 +369,19 @@ class AprsHandler(object):
|
||||
|
||||
|
||||
class Services(object):
|
||||
handlers = []
|
||||
@staticmethod
|
||||
def start():
|
||||
if not PropertyManager.getSharedInstance()["services_enabled"]:
|
||||
return
|
||||
for source in SdrService.getSources().values():
|
||||
ServiceHandler(source)
|
||||
Services.handlers.append(ServiceHandler(source))
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
for handler in Services.handlers:
|
||||
handler.stopServices()
|
||||
Services.handlers = []
|
||||
|
||||
|
||||
class Service(object):
|
||||
|
@ -75,10 +75,15 @@ class SdrService(object):
|
||||
@staticmethod
|
||||
def getSource(id=None):
|
||||
SdrService.loadProps()
|
||||
sources = SdrService.getSources()
|
||||
if not sources:
|
||||
return None
|
||||
if id is None:
|
||||
# TODO: configure default sdr in config? right now it will pick the first one off the list.
|
||||
id = list(SdrService.sdrProps.keys())[0]
|
||||
sources = SdrService.getSources()
|
||||
id = list(sources.keys())[0]
|
||||
|
||||
if not id in sources:
|
||||
return None
|
||||
return sources[id]
|
||||
|
||||
@staticmethod
|
||||
@ -89,17 +94,15 @@ class SdrService(object):
|
||||
props = SdrService.sdrProps[id]
|
||||
className = "".join(x for x in props["type"].title() if x.isalnum()) + "Source"
|
||||
cls = getattr(sys.modules[__name__], className)
|
||||
SdrService.sources[id] = cls(props, SdrService.getNextPort())
|
||||
return SdrService.sources
|
||||
|
||||
|
||||
class SdrSourceException(Exception):
|
||||
pass
|
||||
SdrService.sources[id] = cls(id, props, SdrService.getNextPort())
|
||||
return {key: s for key, s in SdrService.sources.items() if not s.isFailed()}
|
||||
|
||||
|
||||
class SdrSource(object):
|
||||
def __init__(self, props, port):
|
||||
def __init__(self, id, props, port):
|
||||
self.id = id
|
||||
self.props = props
|
||||
self.profile_id = None
|
||||
self.activateProfile()
|
||||
self.rtlProps = self.props.collect(
|
||||
"samp_rate", "nmux_memory", "center_freq", "ppm", "rf_gain", "lna_gain", "rf_amp", "antenna", "if_gain"
|
||||
@ -118,6 +121,7 @@ class SdrSource(object):
|
||||
self.spectrumThread = None
|
||||
self.process = None
|
||||
self.modificationLock = threading.Lock()
|
||||
self.failed = False
|
||||
|
||||
# override this in subclasses
|
||||
def getCommand(self):
|
||||
@ -131,7 +135,10 @@ class SdrSource(object):
|
||||
profiles = self.props["profiles"]
|
||||
if profile_id is None:
|
||||
profile_id = list(profiles.keys())[0]
|
||||
if profile_id == self.profile_id:
|
||||
return;
|
||||
logger.debug("activating profile {0}".format(profile_id))
|
||||
self.profile_id = profile_id
|
||||
profile = profiles[profile_id]
|
||||
for (key, value) in profile.items():
|
||||
# skip the name, that would overwrite the source name.
|
||||
@ -139,6 +146,12 @@ class SdrSource(object):
|
||||
continue
|
||||
self.props[key] = value
|
||||
|
||||
def getId(self):
|
||||
return self.id
|
||||
|
||||
def getProfileId(self):
|
||||
return self.profile_id
|
||||
|
||||
def getProfiles(self):
|
||||
return self.props["profiles"]
|
||||
|
||||
@ -213,17 +226,23 @@ class SdrSource(object):
|
||||
except:
|
||||
time.sleep(0.1)
|
||||
|
||||
if not available:
|
||||
self.failed = True
|
||||
|
||||
self.modificationLock.release()
|
||||
|
||||
if not available:
|
||||
raise SdrSourceException("rtl source failed to start up")
|
||||
|
||||
for c in self.clients:
|
||||
c.onSdrAvailable()
|
||||
if self.failed:
|
||||
c.onSdrFailed()
|
||||
else:
|
||||
c.onSdrAvailable()
|
||||
|
||||
def isAvailable(self):
|
||||
return self.monitor is not None
|
||||
|
||||
def isFailed(self):
|
||||
return self.failed
|
||||
|
||||
def stop(self):
|
||||
for c in self.clients:
|
||||
c.onSdrUnavailable()
|
||||
@ -291,9 +310,12 @@ class Resampler(SdrSource):
|
||||
props["samp_rate"] = if_samp_rate
|
||||
|
||||
self.sdr = sdr
|
||||
super().__init__(props, port)
|
||||
super().__init__(None, props, port)
|
||||
|
||||
def start(self):
|
||||
if self.isFailed():
|
||||
return
|
||||
|
||||
self.modificationLock.acquire()
|
||||
if self.monitor:
|
||||
self.modificationLock.release()
|
||||
@ -353,13 +375,16 @@ class Resampler(SdrSource):
|
||||
except:
|
||||
time.sleep(0.1)
|
||||
|
||||
if not available:
|
||||
self.failed = True
|
||||
|
||||
self.modificationLock.release()
|
||||
|
||||
if not available:
|
||||
raise SdrSourceException("resampler source failed to start up")
|
||||
|
||||
for c in self.clients:
|
||||
c.onSdrAvailable()
|
||||
if self.failed:
|
||||
c.onSdrFailed()
|
||||
else:
|
||||
c.onSdrAvailable()
|
||||
|
||||
def activateProfile(self, profile_id=None):
|
||||
pass
|
||||
@ -493,6 +518,9 @@ class SpectrumThread(csdr.output):
|
||||
def onSdrUnavailable(self):
|
||||
self.dsp.stop()
|
||||
|
||||
def onSdrFailed(self):
|
||||
self.dsp.stop()
|
||||
|
||||
|
||||
class DspManager(csdr.output):
|
||||
def __init__(self, handler, sdrSource):
|
||||
@ -627,6 +655,11 @@ class DspManager(csdr.output):
|
||||
logger.debug("received onSdrUnavailable, shutting down DspSource")
|
||||
self.dsp.stop()
|
||||
|
||||
def onSdrFailed(self):
|
||||
logger.debug("received onSdrFailed, shutting down DspSource")
|
||||
self.dsp.stop()
|
||||
self.handler.handleSdrFailure("sdr device failed")
|
||||
|
||||
|
||||
class CpuUsageThread(threading.Thread):
|
||||
sharedInstance = None
|
||||
|
@ -16,11 +16,19 @@ OPCODE_PING = 0x09
|
||||
OPCODE_PONG = 0x0A
|
||||
|
||||
|
||||
class IncompleteRead(Exception):
|
||||
class WebSocketException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Drained(Exception):
|
||||
class IncompleteRead(WebSocketException):
|
||||
pass
|
||||
|
||||
|
||||
class Drained(WebSocketException):
|
||||
pass
|
||||
|
||||
|
||||
class WebSocketClosed(WebSocketException):
|
||||
pass
|
||||
|
||||
|
||||
@ -42,17 +50,16 @@ class WebSocketConnection(object):
|
||||
(self.interruptPipeRecv, self.interruptPipeSend) = Pipe(duplex=False)
|
||||
self.open = True
|
||||
self.sendLock = threading.Lock()
|
||||
my_headers = self.handler.headers.items()
|
||||
my_header_keys = list(map(lambda x: x[0], my_headers))
|
||||
h_key_exists = lambda x: my_header_keys.count(x)
|
||||
h_value = lambda x: my_headers[my_header_keys.index(x)][1]
|
||||
if (
|
||||
(not h_key_exists("Upgrade"))
|
||||
or not (h_value("Upgrade") == "websocket")
|
||||
or (not h_key_exists("Sec-WebSocket-Key"))
|
||||
):
|
||||
raise WebSocketException
|
||||
ws_key = h_value("Sec-WebSocket-Key")
|
||||
|
||||
headers = {key.lower(): value for key, value in self.handler.headers.items()}
|
||||
if not "upgrade" in headers:
|
||||
raise WebSocketException("Upgrade header not found")
|
||||
if headers["upgrade"].lower() != "websocket":
|
||||
raise WebSocketException("Upgrade header does not contain expected value")
|
||||
if not "sec-websocket-key" in headers:
|
||||
raise WebSocketException("Websocket key not provided")
|
||||
|
||||
ws_key = headers["sec-websocket-key"]
|
||||
shakey = hashlib.sha1()
|
||||
shakey.update("{ws_key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11".format(ws_key=ws_key).encode())
|
||||
ws_key_toreturn = base64.b64encode(shakey.digest())
|
||||
@ -94,6 +101,8 @@ class WebSocketConnection(object):
|
||||
return bytes([ws_first_byte, size])
|
||||
|
||||
def send(self, data):
|
||||
if not self.open:
|
||||
raise WebSocketClosed()
|
||||
# convenience
|
||||
if type(data) == dict:
|
||||
# allow_nan = False disallows NaN and Infinty to be encoded. Browser JSON will not parse them anyway.
|
||||
@ -140,6 +149,26 @@ class WebSocketConnection(object):
|
||||
def interrupt(self):
|
||||
self.interruptPipeSend.send(bytes(0x00))
|
||||
|
||||
def handle(self):
|
||||
WebSocketConnection.connections.append(self)
|
||||
try:
|
||||
self.read_loop()
|
||||
finally:
|
||||
logger.debug("websocket loop ended; shutting down")
|
||||
|
||||
self.messageHandler.handleClose()
|
||||
self.cancelPing()
|
||||
|
||||
logger.debug("websocket loop ended; sending close frame")
|
||||
|
||||
header = self.get_header(0, OPCODE_CLOSE)
|
||||
self._sendBytes(header)
|
||||
|
||||
try:
|
||||
WebSocketConnection.connections.remove(self)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def read_loop(self):
|
||||
def protected_read(num):
|
||||
data = self.handler.rfile.read(num)
|
||||
@ -149,10 +178,9 @@ class WebSocketConnection(object):
|
||||
raise IncompleteRead()
|
||||
return data
|
||||
|
||||
WebSocketConnection.connections.append(self)
|
||||
self.open = True
|
||||
while self.open:
|
||||
(read, _, _) = select.select([self.interruptPipeRecv, self.handler.rfile], [], [])
|
||||
(read, _, _) = select.select([self.interruptPipeRecv, self.handler.rfile], [], [], 15)
|
||||
if self.handler.rfile in read:
|
||||
available = True
|
||||
self.resetPing()
|
||||
@ -190,25 +218,10 @@ class WebSocketConnection(object):
|
||||
except IncompleteRead:
|
||||
logger.warning("incomplete read on websocket; closing connection")
|
||||
self.open = False
|
||||
except TimeoutError:
|
||||
logger.warning("websocket timed out; closing connection")
|
||||
except OSError:
|
||||
logger.exception("OSError while reading data; closing connection")
|
||||
self.open = False
|
||||
|
||||
logger.debug("websocket loop ended; shutting down")
|
||||
|
||||
self.messageHandler.handleClose()
|
||||
self.cancelPing()
|
||||
|
||||
logger.debug("websocket loop ended; sending close frame")
|
||||
|
||||
header = self.get_header(0, OPCODE_CLOSE)
|
||||
self._sendBytes(header)
|
||||
|
||||
try:
|
||||
WebSocketConnection.connections.remove(self)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
self.open = False
|
||||
self.interrupt()
|
||||
@ -233,7 +246,3 @@ class WebSocketConnection(object):
|
||||
def sendPong(self):
|
||||
header = self.get_header(0, OPCODE_PONG)
|
||||
self._sendBytes(header)
|
||||
|
||||
|
||||
class WebSocketException(Exception):
|
||||
pass
|
||||
|
Reference in New Issue
Block a user