6 Commits

Author SHA1 Message Date
4fb99c05e0 fix /status http response 2020-06-21 19:39:57 +02:00
d84e672e6e README fix 2019-12-29 23:31:23 +01:00
a687d7f163 README fix 2019-12-29 23:30:40 +01:00
9f1c9a0a09 README fix 2019-12-29 23:30:01 +01:00
f531f3c464 README fix 2019-12-29 23:25:39 +01:00
d3161b6fc1 final version 2019-12-29 23:21:44 +01:00
196 changed files with 17891 additions and 16536 deletions

View File

@ -1,7 +0,0 @@
.git
.gitignore
.idea
**/*.pyc
**/*.swp
black-env
debian

6
.gitignore vendored
View File

@ -1,5 +1,3 @@
**/*.pyc *.pyc
**/*.swp *.swp
tags tags
.idea
packages

View File

@ -1,150 +0,0 @@
**0.19.1**
- Added ability to authenticate receivers with listing sites using "receiver id" tokens
**0.19.0**
- Fix direwolf connection setup by implementing a retry loop
- Pass direct sampling mode changes for rtl_sdr_soapy to owrx_connector
- OSM maps instead of Google when google_maps_api_key is not set (thanks @jquagga)
- Improved logic to pass parameters to soapy devices.
- `rtl_sdr_soapy`: added support for `bias_tee`
- `sdrplay`: added support for `bias_tee`, `rf_notch` and `dab_notch`
- `airspy`: added support for `bitpack`
- Added support for Perseus-SDR devices, (thanks @amontefusco)
- Property System has been rewritten so that defaults on sdr behave as expected
- Waterfall range auto-adjustment now only takes the center 80% of the spectrum into account, which should work better
with SDRs that oversample or have rather flat filter curves towards the spectrum edges
- Bugfix for negative network usage
- FiFi SDR: prevent arecord from shutting down after 2GB of data has been sent
- Added support for bias tee control on rtl_sdr devices
- All connector driven SDRs now support `"rf_gain": "auto"` to enable AGC
- `rtl_sdr` type now also supports the `direct_sampling` option
- Added decoding implementation for for digimode "JS8Call"
(requires an installation of [js8call](http://js8call.com/) and
[the js8py library](https://github.com/jketterl/js8py))
- Reorganization of the frontend demodulator code
- Improve receiver load time by concatenating javascript assets
- Docker images migrated to Debian slim images; This was necessary to allow the use of function multiversioning in
csdr and owrx_connector to allow the images to run on a wider range of CPUs
- Docker containers have been updated to include the SDRplay driver version 3
- HackRF support is now based on SoapyHackRF
- Removed sdr.hu server listing support since the site has been shut down
- Added support for Radioberry 2 Rasbperry Pi SDR Cape
**0.18.0**
- Support for SoapyRemote
**2020-02-08**
- Compression, resampling and filtering in the frontend have been rewritten in javascript, sdr.js has been removed
- Decoding of Pocsag modulation is now possible
- Removed the 3D waterfall since it had no real application and required ~1MB of javascript code to be downloaded
- Improved the frontend handling of the "too many users" scenario
- PSK63 digimode is now available (same decoding pipeline as PSK31, but with adopted parameters)
- The frequency can now be manipulated with the mousewheel, which should allow the user to tune more precise. The tuning
step size is determined by the digit the mouse cursor is hovering over.
- Clicking on the frequency now opens an input for direct frequency selection
- URL hashes have been fixed and improved: They are now updated automatically, so a shared URL will include frequency
and demodulator, which allows for improved sharing and linking.
- New daylight scheduler for background decoding, allows profiles to be selected by local sunrise / sunset times
- New devices supported:
- LimeSDR (`"type": "lime_sdr"`)
- PlutoSDR (`"type": "pluto_sdr"`)
- RTL_SDR via Soapy (`"type": "rtl_sdr_soapy"`) on special request to allow use of the direct sampling mode
**2020-01-04**
- The [owrx_connector](https://github.com/jketterl/owrx_connector) is now the default way of communicating with sdr
devices. The old sdr types have been replaced, all `_connector` suffixes on the type must be removed!
- The sources have been refactored, making it a lot easier to add support for other devices
- SDR device failure handling has been improved, including user feedback
- New devices supported:
- FiFiSDR (`"type": "fifi_sdr"`)
**2019-12-15**
- wsjt-x updated to 2.1.2
- The rtl_tcp compatibility mode of the owrx_connector is now configurable using the `rtltcp_compat` flag
**2019-12-10**
- added support for airspyhf devices (Airspy HF+ / Discovery)
**2019-12-05**
- explicit device filter for soapy devices for multi-device setups
**2019-12-03**
- compatibility fixes for safari browsers (ios and mac)
**2019-11-24**
- There is now a new way to interface with SDR hardware, .
They talk directly to the hardware (no rtl_sdr / rx_sdr necessary) and offer I/Q data on a socket, just like nmux
did before. They additionally offer a control socket that allows openwebrx to control the SDR parameters directly,
without the need for repeated restarts. This allows for quicker profile changes, and also reduces the risk of your
SDR hardware from failing during the switchover. See `config_webrx.py` for further information and instructions.
- Offset tuning using the `lfo_offset` has been reworked in a way that `center_freq` has to be set to the frequency you
actually want to listen to. If you're using an `lfo_offset` already, you will probably need to change its sign.
- `initial_squelch_level` can now be set on each profile.
- As usual, plenty of fixes and improvements.
**2019-10-27**
- Part of the frontend code has been reworked
- Audio buffer minimums have been completely stripped. As a result, you should get better latency. Unfortunately,
this also means there will be some skipping when audio starts.
- Now also supports AudioWorklets (for those browser that have it). The Raspberry Pi image has been updated to include
https due to the SecureContext requirement.
- Mousewheel controls for the receiver sliders
- Error handling for failed SDR devices
**2019-09-29**
- One of the most-requested features is finally coming to OpenWebRX: Bookmarks (sometimes also referred to as labels).
There's two kinds of bookmarks available:
- Serverside bookmarks that are set up by the receiver administrator. Check the file `bookmarks.json` for examples!
- Clientside bookmarks which every user can store for themselves. They are stored in the browser's localStorage.
- Some more bugs in the websocket handling have been fixed.
**2019-09-25**
- Automatic reporting of spots to [pskreporter](https://pskreporter.info/) is now possible. Please have a look at the
configuration on how to set it up.
- Websocket communication has been overhauled in large parts. It should now be more reliable, and failing connections
should now have no impact on other users.
- Profile scheduling allows to set up band-hopping if you are running background services.
- APRS now has the ability to show symbols on the map, if a corresponding symbol set has been installed. Check the
config!
- Debug logging has been disabled in a handful of modules, expect vastly reduced output on the shell.
**2019-09-13**
- New set of APRS-related features
- Decode Packet transmissions using [direwolf](https://github.com/wb2osz/direwolf) (1k2 only for now)
- APRS packets are mostly decoded and shown both in a new panel and on the map
- APRS is also available as a background service
- direwolfs I-gate functionality can be enabled, which allows your receiver to work as a receive-only I-gate for the
APRS network in the background
- Demodulation for background services has been optimized to use less total bandwidth, saving CPU
- More metrics have been added; they can be used together with collectd and its curl_json plugin for now, with some
limitations.
**2019-07-21**
- Latest Features:
- More WSJT-X modes have been added, including the new FT4 mode
- I started adding a bandplan feature, the first thing visible is the "dial" indicator that brings you right to the
dial frequency for digital modes
- fixed some bugs in the websocket communication which broke the map
**2019-07-13**
- Latest Features:
- FT8 Integration (using wsjt-x demodulators)
- New Map Feature that shows both decoded grid squares from FT8 and Locations decoded from YSF digital voice
- New Feature report that will show what functionality is available
- There's a new Raspbian SD Card image available (see below)
**2019-06-30**
- I have done some major rework on the openwebrx core, and I am planning to continue adding more features in the near
future. Please check this place for updates.
- My work has not been accepted into the upstream repository, so you will need to chose between my fork and the official
version.
- I have enabled the issue tracker on this project, so feel free to file bugs or suggest enhancements there!
- This version sports the following new and amazing features:
- Support of multiple SDR devices simultaneously
- Support for multiple profiles per SDR that allow the user to listen to different frequencies
- Support for digital voice decoding
- Feature detection that will disable functionality when dependencies are not available (if you're missing the digital
buttons, this is probably why)
- Raspbian SD Card Images and Docker builds available (see below)
- I am currently working on the feature set for a stable release, but you are more than welcome to test development
versions!

15
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,15 @@
First of all, thank you for taking the time to contribute to this project!
Before I can accept your contributions, I need a signed copy of the Individual Contributor License Agreement (ICLA) from you, which is available <a href="ICLA.txt">here</a>.
The ICLA is needed because it will allow me to dual license the OpenWebRX project under AGPL and a commercial license.
I will also apply dual licensing to csdr, but only those parts that are original work (e.g. without the parts enabled by `-DUSE_IMA_ADPCM`; code taken from other projects is clearly separable).
However, even if there is commercial interest in the projects, I promise to keep them as open as possible, keeping my original intention to provide an open-source web-based SDR receiver software to the amateur radio operators and SDR enthusiasts.
This contributor agreement is based on the one of Apache Software Foundation, with some modifications. (You can review differences <a href="https://gist.github.com/ha7ilm/9e981006d24659e336c7/revisions">here</a>).
When you contribute for the first time, I will send you the ICLA. Replying with only the information requested and the text "I Agree" is sufficient.
Thanks,
Andras, HA7ILM

5
CONTRIBUTORS Normal file
View File

@ -0,0 +1,5 @@
This is a list of the great people who contributed code to the OpenWebRX repository. (Names are sorted alphabetically.)
Gnoxter <gnoxter@linuxlounge.net>
John Seamons, ZL/KF6VO <jks@jks.com>

128
ICLA.txt Normal file
View File

@ -0,0 +1,128 @@
Individual Contributor License Agreement ("Agreement")
In order to clarify the intellectual property license granted
with Contributions from any person or entity, Retzler András
(hereinafter referred to as "Project Owner") must have a
Contributor License Agreement ("CLA") on file that has
been signed by each Contributor, indicating agreement to the license
terms below. This license is for your protection as a Contributor as
well as the protection of the Project Owner; it does not change your
rights to use your own Contributions for any other purpose.
Please read this document carefully before signing and keep a copy
for your records.
Full name: ______________________________________________________
(optional) Public name: _________________________________________
Mailing Address: ________________________________________________
________________________________________________
Country: ______________________________________________________
(optional) Telephone: ___________________________________________
E-Mail: ______________________________________________________
You accept and agree to the following terms and conditions for Your
present and future Contributions submitted to the Project Owner.
Except for the license granted herein to the Project Owner and recipients
of software distributed by the Project Owner, You reserve all right, title,
and interest in and to Your Contributions.
1. Definitions.
"You" (or "Your") shall mean the copyright owner or legal entity
authorized by the copyright owner that is making this Agreement
with the Project Owner. For legal entities, the entity making a
Contribution and all other entities that control, are controlled
by, or are under common control with that entity are considered to
be a single Contributor. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"Contribution" shall mean any original work of authorship,
including any modifications or additions to an existing work, that
is intentionally submitted by You to the Project Owner for inclusion
in, or documentation of, any of the products owned or managed by
the Project Owner (the "Work"). For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written
communication sent to the Project Owner or its representatives,
including but not limited to communication on electronic mailing
lists, source code control systems, and issue tracking systems that
are managed by, or on behalf of, the Project Owner for the purpose of
discussing and improving the Work, but excluding communication that
is conspicuously marked or otherwise designated in writing by You
as "Not a Contribution."
2. Grant of Copyright License. Subject to the terms and conditions of
this Agreement, You hereby grant to the Project Owner and to
recipients of software distributed by the Project Owner a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare derivative works of,
publicly display, publicly perform, sublicense, and distribute Your
Contributions and such derivative works.
3. Grant of Patent License. Subject to the terms and conditions of
this Agreement, You hereby grant to the Project Owner and to
recipients of software distributed by the Project Owner a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the
Work, where such license applies only to those patent claims
licensable by You that are necessarily infringed by Your
Contribution(s) alone or by combination of Your Contribution(s)
with the Work to which such Contribution(s) was submitted. If any
entity institutes patent litigation against You or any other entity
(including a cross-claim or counterclaim in a lawsuit) alleging
that your Contribution, or the Work to which you have contributed,
constitutes direct or contributory patent infringement, then any
patent licenses granted to that entity under this Agreement for
that Contribution or Work shall terminate as of the date such
litigation is filed.
4. You represent that you are legally entitled to grant the above
license. If your employer(s) has rights to intellectual property
that you create that includes your Contributions, you represent
that you have received permission to make Contributions on behalf
of that employer, that your employer has waived such rights for
your Contributions to the Project Owner, or that your employer has
executed a separate Corporate CLA with the Project Owner.
5. You represent that each of Your Contributions is Your original
creation (see section 7 for submissions on behalf of others). You
represent that Your Contribution submissions include complete
details of any third-party license or other restriction (including,
but not limited to, related patents and trademarks) of which you
are personally aware and which are associated with any part of Your
Contributions.
6. You are not expected to provide support for Your Contributions,
except to the extent You desire to provide support. You may provide
support for free, for a fee, or not at all. Unless required by
applicable law or agreed to in writing, You provide Your
Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied, including, without
limitation, any warranties or conditions of TITLE, NON-
INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
7. Should You wish to submit work that is not Your original creation,
You may submit it to the Project Owner separately from any
Contribution, identifying the complete details of its source and of
any license or other restriction (including, but not limited to,
related patents, trademarks, and license agreements) of which you
are personally aware, and conspicuously marking the work as
"Submitted on behalf of a third-party: [named here]".
8. You agree to notify the Project Owner of any facts or circumstances of
which you become aware that would make these representations
inaccurate in any respect.
Please sign: __________________________________ Date: ________________
Text derived from the Apache Individual Contributor License Agreement
("Agreement") V2.0, available at http://apache.org/licenses/icla.txt

115
README.md
View File

@ -1,39 +1,94 @@
OpenWebRX # OpenWebRX
=========
OpenWebRX is a multi-user SDR receiver software with a web interface. OpenWebRX is a multi-user SDR receiver software with a web interface.
![OpenWebRX](https://www.openwebrx.de/gfx/openwebrx-screenshot.png) ----
### ⚠️ From 2019-12-29 OpenWebRX development is discontinued. ⚠️
I'm would like to say a big thanks to everyone who supported me during this project, including those who contributed either code or donations. It has been a very fruitful 6 years, but now it's time to move on to other projects. See also my [blog](https://blog.sdr.hu) about that.
(@simonyiszk, please keep this GitHub repo for historic purposes.)
Know limitations of the last version:
- Python 2.7, a main dependency of the project, will be not be officially maintained from 1 January 2020. By time, probably it will not be secure to use this version on public servers, unless someone still provides security patches for Python 2.
- Some specific parts of the DSP code could be improved for better SNR.
Even though these limitations are probably acceptable in an amateur radio project, I would not build critical infrastructure on it.
For commercial inquiries (e.g. if someone wants me to develop an improved version without these limitations), I'm still open, [drop me an e-mail](mailto:randras@sdr.hu).
----
[:floppy_disk: Setup guide for Ubuntu](http://blog.sdr.hu/2015/06/30/quick-setup-openwebrx.html) | [:blue_book: Knowledge base on the Wiki](https://github.com/simonyiszk/openwebrx/wiki/) | [:earth_americas: Receivers on SDR.hu](http://sdr.hu/)
![OpenWebRX](http://blog.sdr.hu/images/openwebrx/screenshot.png)
It has the following features: It has the following features:
- [csdr](https://github.com/jketterl/csdr) based demodulators (AM/FM/SSB/CW/BPSK31/BPSK63) - <a href="https://github.com/simonyiszk/csdr">csdr</a> based demodulators (AM/FM/SSB/CW/BPSK31),
- filter passband can be set from GUI - filter passband can be set from GUI,
- it extensively uses HTML5 features like WebSocket, Web Audio API, and Canvas - waterfall display can be shifted back in time,
- it works in Google Chrome, Chromium and Mozilla Firefox - it extensively uses HTML5 features like WebSocket, Web Audio API, and &lt;canvas&gt;,
- currently supports RTL-SDR, HackRF, SDRplay, AirSpy, LimeSDR, PlutoSDR - it works in Google Chrome, Chromium (above version 37) and Mozilla Firefox (above version 28),
- Multiple SDR devices can be used simultaneously - currently supports RTL-SDR, HackRF, SDRplay, AirSpy and many other devices, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/">OpenWebRX Wiki</a>,
- [digiham](https://github.com/jketterl/digiham) based demodularors (DMR, YSF, Pocsag) - it has a 3D waterfall display:
- [dsd](https://github.com/f4exb/dsdcc) based demodulators (D-Star, NXDN)
- [wsjt-x](https://physics.princeton.edu/pulsar/k1jt/wsjtx.html) based demodulators (FT8, FT4, WSPR, JT65, JT9) ![OpenWebRX 3D waterfall](http://blog.sdr.hu/images/openwebrx/screenshot-3d.gif)
**News (2015-08-18)**
- My BSc. thesis written on OpenWebRX is <a href="https://sdr.hu/static/bsc-thesis.pdf">available here.</a>
- Several bugs were fixed to improve reliability and stability.
- OpenWebRX now supports compression of audio and waterfall stream, so the required network uplink bandwidth has been decreased from 2 Mbit/s to about 200 kbit/s per client! (Measured with the default settings. It is also dependent on `fft_size`.)
- OpenWebRX now uses <a href="https://github.com/simonyiszk/csdr#sdrjs">sdr.js</a> (*libcsdr* compiled to JavaScript) for some client-side DSP tasks.
- Receivers can now be listed on <a href="http://sdr.hu/">SDR.hu</a>.
- License for OpenWebRX is now Affero GPL v3.
**News (2016-02-14)**
- The DDC in *csdr* has been manually optimized for ARM NEON, so it runs around 3 times faster on the Raspberry Pi 2 than before.
- Also we use *ncat* instead of *rtl_mus*, and it is 3 times faster in some cases.
- OpenWebRX now supports URLs like: `http://localhost:8073/#freq=145555000,mod=usb`
- UI improvements were made, thanks to John Seamons and Gnoxter.
**News (2017-04-04)**
- *ncat* has been replaced with a custom implementation called *nmux* due to a bug that caused regular crashes on some machines. The *nmux* tool is part of the *csdr* package.
- Most consumer SDR devices are supported via <a href="https://github.com/rxseger/rx_tools">rx_tools</a>, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/Using-rx_tools-with-OpenWebRX">OpenWebRX Wiki</a> on that.
**News (2017-07-12)**
- OpenWebRX now has a BPSK31 demodulator and a 3D waterfall display.
> When upgrading OpenWebRX, please make sure that you also upgrade *csdr*!
## OpenWebRX servers on SDR.hu
[SDR.hu](http://sdr.hu) is a site which lists the active, public OpenWebRX servers. Your receiver [can also be part of it](http://sdr.hu/openwebrx), if you want.
![sdr.hu](http://blog.sdr.hu/images/openwebrx/screenshot-sdrhu.png)
## Setup ## Setup
The following methods of setting up a receiver are currently available: OpenWebRX currently requires Linux and python 2.7 to run.
- Raspberry Pi SD card images First you will need to install the dependencies:
- Debian repository
- Docker images
- Manual installation
Please checkout the [setup guide on the wiki](https://github.com/jketterl/openwebrx/wiki/Setup-Guide) for more details - <a href="https://github.com/simonyiszk/csdr">libcsdr</a>
on the respective methods. - <a href="http://sdr.osmocom.org/trac/wiki/rtl-sdr">rtl-sdr</a>
## Community After cloning this repository and connecting an RTL-SDR dongle to your computer, you can run the server:
If you have trouble setting up or configuring your receiver, you have some great idea you want to see implemented, or python openwebrx.py
you just generally want to have some OpenWebRX-related chat, come visit us over on
[our groups.io group](https://groups.io/g/openwebrx). You can now open the GUI at <a href="http://localhost:8073">http://localhost:8073</a>.
Please note that the server is also listening on the following ports (on localhost only):
- port 4951 for the multi-user I/Q server.
Now the next step is to customize the parameters of your server in `config_webrx.py`.
Actually, if you do something cool with OpenWebRX, please drop me a mail:
*Andras Retzler, HA7ILM &lt;randras@sdr.hu&gt;*
## Usage tips ## Usage tips
@ -43,10 +98,16 @@ The filter envelope can be dragged at its ends and moved around to set the passb
However, if you hold down the shift key, you can drag the center line (BFO) or the whole passband (PBS). However, if you hold down the shift key, you can drag the center line (BFO) or the whole passband (PBS).
## Setup tips
If you have any problems installing OpenWebRX, you should check out the <a href="https://github.com/simonyiszk/openwebrx/wiki">Wiki</a> about it, which has a page on the <a href="https://github.com/simonyiszk/openwebrx/wiki/Common-problems-and-their-solutions">common problems and their solutions</a>.
Sometimes the actual error message is not at the end of the terminal output, you may have to look at the whole output to find it.
If you want to run OpenWebRX on a remote server instead of *localhost*, do not forget to set *server_hostname* in `config_webrx.py`.
## Licensing ## Licensing
OpenWebRX is available under Affero GPL v3 license OpenWebRX is available under Affero GPL v3 license (<a href="https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)">summary</a>).
([summary](https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0))).
OpenWebRX is also available under a commercial license on request. Please contact me at the address OpenWebRX is also available under a commercial license on request. Please contact me at the address *&lt;randras@sdr.hu&gt;* for licensing options.
*&lt;randras@sdr.hu&gt;* for licensing options.

View File

@ -1,273 +0,0 @@
[
{
"name": "160m",
"lower_bound": 1810000,
"upper_bound": 2000000,
"frequencies": {
"psk31": 1838000,
"ft8": 1840000,
"wspr": 1836600,
"jt65": 1838000,
"jt9": 1839000,
"js8": 1842000
}
},
{
"name": "80m",
"lower_bound": 3500000,
"upper_bound": 3800000,
"frequencies": {
"psk31": 3580000,
"ft8": 3573000,
"wspr": 3592600,
"jt65": 3570000,
"jt9": 3572000,
"ft4": [3568000, 3575000],
"js8": 3578000
}
},
{
"name": "60m",
"lower_bound": 5351500,
"upper_bound": 5366500,
"frequencies": {
"ft8": 5357000,
"wspr": 5364700
}
},
{
"name": "40m",
"lower_bound": 7000000,
"upper_bound": 7200000,
"frequencies": {
"psk31": 7040000,
"ft8": 7074000,
"wspr": 7038600,
"jt65": 7076000,
"jt9": 7078000,
"ft4": 7047500,
"js8": 7078000
}
},
{
"name": "30m",
"lower_bound": 10100000,
"upper_bound": 10150000,
"frequencies": {
"psk31": 10141000,
"ft8": 10136000,
"wspr": 10138700,
"jt65": 10138000,
"jt9": 10140000,
"ft4": 10140000,
"js8": 10130000
}
},
{
"name": "20m",
"lower_bound": 14000000,
"upper_bound": 14350000,
"frequencies": {
"psk31": 14070000,
"ft8": 14074000,
"wspr": 14095600,
"jt65": 14076000,
"jt9": 14078000,
"ft4": 14080000,
"js8": 14078000
}
},
{
"name": "17m",
"lower_bound": 18068000,
"upper_bound": 18168000,
"frequencies": {
"psk31": 18098000,
"ft8": 18100000,
"wspr": 18104600,
"jt65": 18102000,
"jt9": 18104000,
"ft4": 18104000,
"js8": 18104000
}
},
{
"name": "15m",
"lower_bound": 21000000,
"upper_bound": 21450000,
"frequencies": {
"psk31": 21070000,
"ft8": 21074000,
"wspr": 21094600,
"jt65": 21076000,
"jt9": 21078000,
"ft4": 21140000,
"js8": 21078000
}
},
{
"name": "12m",
"lower_bound": 24890000,
"upper_bound": 24990000,
"frequencies": {
"psk31": 24920000,
"ft8": 24915000,
"wspr": 24924600,
"jt65": 24917000,
"jt9": 24919000,
"ft4": 24919000,
"js8": 24922000
}
},
{
"name": "10m",
"lower_bound": 28000000,
"upper_bound": 29700000,
"frequencies": {
"psk31": [28070000, 28120000],
"ft8": 28074000,
"wspr": 28124600,
"jt65": 28076000,
"jt9": 28078000,
"ft4": 28180000,
"js8": 28078000
}
},
{
"name": "6m",
"lower_bound": 50030000,
"upper_bound": 51000000,
"frequencies": {
"psk31": 50305000,
"ft8": 50313000,
"wspr": 50293000,
"jt65": 50310000,
"jt9": 50312000,
"ft4": 50318000,
"js8": 50318000
}
},
{
"name": "4m",
"lower_bound": 70150000,
"upper_bound": 70200000,
"frequencies": {
"wspr": 70091000
}
},
{
"name": "2m",
"lower_bound": 144000000,
"upper_bound": 146000000,
"frequencies": {
"wspr": 144489000,
"ft8": 144174000,
"ft4": 144170000,
"jt65": 144120000,
"packet": 144800000
}
},
{
"name": "70cm",
"lower_bound": 430000000,
"upper_bound": 440000000,
"frequencies": {
"pocsag": 439987500
}
},
{
"name": "23cm",
"lower_bound": 1240000000,
"upper_bound": 1300000000
},
{
"name": "13cm",
"lower_bound": 2320000000,
"upper_bound": 2450000000
},
{
"name": "9cm",
"lower_bound": 3400000000,
"upper_bound": 3475000000
},
{
"name": "6cm",
"lower_bound": 5650000000,
"upper_bound": 5850000000
},
{
"name": "3cm",
"lower_bound": 10000000000,
"upper_bound": 10500000000
},
{
"name": "120m Broadcast",
"lower_bound": 2300000,
"upper_bound": 2495000
},
{
"name": "90m Broadcast",
"lower_bound": 3200000,
"upper_bound": 3400000
},
{
"name": "75m Broadcast",
"lower_bound": 3900000,
"upper_bound": 4000000
},
{
"name": "60m Broadcast",
"lower_bound": 4750000,
"upper_bound": 4995000
},
{
"name": "49m Broadcast",
"lower_bound": 5900000,
"upper_bound": 6200000
},
{
"name": "41m Broadcast",
"lower_bound": 7200000,
"upper_bound": 7450000
},
{
"name": "31m Broadcast",
"lower_bound": 9400000,
"upper_bound": 9900000
},
{
"name": "25m Broadcast",
"lower_bound": 11600000,
"upper_bound": 12100000
},
{
"name": "22m Broadcast",
"lower_bound": 13570000,
"upper_bound": 13870000
},
{
"name": "19m Broadcast",
"lower_bound": 15100000,
"upper_bound": 15830000
},
{
"name": "16m Broadcast",
"lower_bound": 17480000,
"upper_bound": 17900000
},
{
"name": "15m Broadcast",
"lower_bound": 18900000,
"upper_bound": 19020000
},
{
"name": "13m Broadcast",
"lower_bound": 21450000,
"upper_bound": 21850000
},
{
"name": "11m Broadcast",
"lower_bound": 25670000,
"upper_bound": 26100000
}
]

View File

@ -1,217 +0,0 @@
[
{
"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": "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"
}
]

View File

@ -1,16 +0,0 @@
#!/bin/bash
set -euxo pipefail
. docker/env
docker build --pull -t openwebrx-base:$ARCHTAG -f docker/Dockerfiles/Dockerfile-base .
docker build --build-arg ARCHTAG=$ARCHTAG -t jketterl/openwebrx-rtlsdr:$ARCHTAG -f docker/Dockerfiles/Dockerfile-rtlsdr .
docker build --build-arg ARCHTAG=$ARCHTAG -t openwebrx-soapysdr-base:$ARCHTAG -f docker/Dockerfiles/Dockerfile-soapysdr .
docker build --build-arg ARCHTAG=$ARCHTAG -t jketterl/openwebrx-sdrplay:$ARCHTAG -f docker/Dockerfiles/Dockerfile-sdrplay .
docker build --build-arg ARCHTAG=$ARCHTAG -t jketterl/openwebrx-hackrf:$ARCHTAG -f docker/Dockerfiles/Dockerfile-hackrf .
docker build --build-arg ARCHTAG=$ARCHTAG -t jketterl/openwebrx-airspy:$ARCHTAG -f docker/Dockerfiles/Dockerfile-airspy .
docker build --build-arg ARCHTAG=$ARCHTAG -t jketterl/openwebrx-rtlsdr-soapy:$ARCHTAG -f docker/Dockerfiles/Dockerfile-rtlsdr-soapy .
docker build --build-arg ARCHTAG=$ARCHTAG -t jketterl/openwebrx-plutosdr:$ARCHTAG -f docker/Dockerfiles/Dockerfile-plutosdr .
docker build --build-arg ARCHTAG=$ARCHTAG -t jketterl/openwebrx-limesdr:$ARCHTAG -f docker/Dockerfiles/Dockerfile-limesdr .
docker build --build-arg ARCHTAG=$ARCHTAG -t jketterl/openwebrx-soapyremote:$ARCHTAG -f docker/Dockerfiles/Dockerfile-soapyremote .
docker build --build-arg ARCHTAG=$ARCHTAG -t jketterl/openwebrx-perseus:$ARCHTAG -f docker/Dockerfiles/Dockerfile-perseus .
docker build --build-arg ARCHTAG=$ARCHTAG -t jketterl/openwebrx-full:$ARCHTAG -t jketterl/openwebrx:$ARCHTAG -f docker/Dockerfiles/Dockerfile-full .

View File

@ -6,7 +6,6 @@ config_webrx: configuration options for OpenWebRX
This file is part of OpenWebRX, This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI. an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu> Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
Copyright (c) 2019-2020 by Jakob Ketterl <dd5jfk@darc.de>
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@ -32,64 +31,59 @@ config_webrx: configuration options for OpenWebRX
and use them for running your web service with OpenWebRX.) and use them for running your web service with OpenWebRX.)
""" """
# configuration version. please only modify if you're able to perform the associated migration steps.
version = 2
# NOTE: you can find additional information about configuring OpenWebRX in the Wiki: # NOTE: you can find additional information about configuring OpenWebRX in the Wiki:
# https://github.com/jketterl/openwebrx/wiki/Configuration-guide # https://github.com/simonyiszk/openwebrx/wiki
# ==== Server settings ==== # ==== Server settings ====
web_port = 8073 web_port=8073
max_clients = 20 server_hostname="localhost" # If this contains an incorrect value, the web UI may freeze on load (it can't open websocket)
max_clients=20
# ==== Web GUI configuration ==== # ==== Web GUI configuration ====
receiver_name = "[Callsign]" receiver_name="[Callsign]"
receiver_location = "Budapest, Hungary" receiver_location="Budapest, Hungary"
receiver_asl = 200 receiver_qra="JN97ML"
receiver_admin = "example@example.com" receiver_asl=200
receiver_gps = {"lat": 47.000000, "lon": 19.000000} receiver_ant="Longwire"
photo_title = "Panorama of Budapest from Schönherz Zoltán Dormitory" receiver_device="RTL-SDR"
# photo_desc allows you to put pretty much any HTML you like into the receiver description. receiver_admin="example@example.com"
# The lines below should give you some examples of what's possible. receiver_gps=(47.000000,19.000000)
photo_desc = """ 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 /> You can add your own background photo and receiver information.<br />
Receiver is operated by: <a href="mailto:openwebrx@localhost" target="_blank">Receiver Operator</a><br/> Receiver is operated by: <a href="mailto:%[RX_ADMIN]">%[RX_ADMIN]</a><br/>
Device: Receiver Device<br /> Device: %[RX_DEVICE]<br />
Antenna: Receiver Antenna<br /> Antenna: %[RX_ANT]<br />
Website: <a href="http://localhost" target="_blank">http://localhost</a> Website: <a href="http://localhost" target="_blank">http://localhost</a>
""" """
# ==== Public receiver listings ==== # ==== sdr.hu listing ====
# You can publish your receiver on online receiver directories, like https://www.receiverbook.de # If you want your ham receiver to be listed publicly on sdr.hu, then take the following steps:
# You will receive a receiver key from the directory that will authenticate you as the operator of this receiver. # 1. Register at: http://sdr.hu/register
# Please note that you not share your receiver keys publicly since anyone that obtains your receiver key can take over # 2. You will get an unique key by email. Copy it and paste here:
# your public listing. sdrhu_key = ""
# Your receiver keys should be placed into this array: # 3. Set this setting to True to enable listing:
receiver_keys = [] sdrhu_public_listing = False
# If you list your receiver on multiple sites, you can place all your keys into the array above, or you can append
# keys to the arraylike this:
# receiver_keys += ["my-receiver-key"]
# If you're not sure, simply copy & paste the code you received from your listing site below this line:
# ==== DSP/RX settings ==== # ==== DSP/RX settings ====
fft_fps = 9 fft_fps=9
fft_size = 4096 # Should be power of 2 fft_size=4096 #Should be power of 2
fft_voverlap_factor = ( fft_voverlap_factor=0.3 #If fft_voverlap_factor is above 0, multiple FFTs will be used for creating a line on the diagram.
0.3 # If fft_voverlap_factor is above 0, multiple FFTs will be used for creating a line on the diagram.
)
audio_compression = "adpcm" # valid values: "adpcm", "none" # samp_rate = 250000
fft_compression = "adpcm" # valid values: "adpcm", "none" samp_rate = 2400000
center_freq = 144250000
rf_gain = 5 #in dB. For an RTL-SDR, rf_gain=0 will set the tuner to auto gain mode, else it will be in manual gain mode.
ppm = 0
digimodes_enable = True # Decoding digimodes come with higher CPU usage. audio_compression="adpcm" #valid values: "adpcm", "none"
digimodes_fft_size = 1024 fft_compression="adpcm" #valid values: "adpcm", "none"
# determines the quality, and thus the cpu usage, for the ambe codec used by digital voice modes digimodes_enable=True #Decoding digimodes come with higher CPU usage.
# if you're running on a Raspi (up to 3B+) you'll want to leave this on 1 digimodes_fft_size=1024
digital_voice_unvoiced_quality = 1
# enables lookup of DMR ids using the radioid api start_rtl_thread=True
digital_voice_dmr_id_lookup = True
""" """
Note: if you experience audio underruns while CPU usage is 100%, you can: Note: if you experience audio underruns while CPU usage is 100%, you can:
@ -102,234 +96,121 @@ Note: if you experience audio underruns while CPU usage is 100%, you can:
# ==== I/Q sources ==== # ==== I/Q sources ====
# (Uncomment the appropriate by removing # characters at the beginning of the corresponding lines.) # (Uncomment the appropriate by removing # characters at the beginning of the corresponding lines.)
############################################################################### #################################################################################################
# Is my SDR hardware supported? # # Is my SDR hardware supported? #
# Check here: https://github.com/jketterl/openwebrx/wiki/Supported-Hardware # # Check here: https://github.com/simonyiszk/openwebrx/wiki#guides-for-receiver-hardware-support #
############################################################################### #################################################################################################
# Currently supported types of sdr receivers: # You can use other SDR hardware as well, by giving your own command that outputs the I/Q samples... Some examples of configuration are available here (default is RTL-SDR):
# "rtl_sdr", "rtl_sdr_soapy", "sdrplay", "hackrf", "airspy", "airspyhf", "fifi_sdr",
# "perseussdr", "lime_sdr", "pluto_sdr", "soapy_remote"
#
# In order to use rtl_sdr, you will need to install librtlsdr-dev and the connector.
# In order to use sdrplay, airspy or airspyhf, you will need to install soapysdr, the corresponding driver, and the
# connector.
#
# https://github.com/jketterl/owrx_connector
#
# In order to use Perseus HF you need to install the libperseus-sdr
#
# https://github.com/Microtelecom/libperseus-sdr
#
# and do the proper changes to the sdrs object below
# (see also Wiki in https://github.com/jketterl/openwebrx/wiki/Sample-configuration-for-Perseus-HF-receiver).
#
sdrs = { # >> RTL-SDR via rtl_sdr
"rtlsdr": { start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
"name": "RTL-SDR USB Stick", format_conversion="csdr convert_u8_f"
"type": "rtl_sdr",
"ppm": 0, #lna_gain=8
# you can change this if you use an upconverter. formula is: #rf_amp=1
# center_freq + lfo_offset = actual frequency on the sdr #start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l{lna_gain} -a{rf_amp} -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm, rf_amp=rf_amp, lna_gain=lna_gain)
# "lfo_offset": 0, #format_conversion="csdr convert_s8_f"
"profiles": { """
"70cm": { To use a HackRF, compile the HackRF host tools from its "stdout" branch:
"name": "70cm Relais", git clone https://github.com/mossmann/hackrf/
"center_freq": 438800000, cd hackrf
"rf_gain": 30, git fetch
"samp_rate": 2400000, git checkout origin/stdout
"start_freq": 439275000, cd host
"start_mod": "nfm", mkdir build
}, cd build
"2m": { cmake .. -DINSTALL_UDEV_RULES=ON
"name": "2m komplett", make
"center_freq": 145000000, sudo make install
"rf_gain": 30, """
"samp_rate": 2400000,
"start_freq": 145725000, # >> Sound card SDR (needs ALSA)
"start_mod": "nfm", # I did not have the chance to properly test it.
}, #samp_rate = 96000
}, #start_rtl_command="arecord -f S16_LE -r {samp_rate} -c2 -".format(samp_rate=samp_rate)
}, #format_conversion="csdr convert_s16_f | csdr gain_ff 30"
"airspy": {
"name": "Airspy HF+", # >> /dev/urandom test signal source
"type": "airspyhf", # samp_rate = 2400000
"ppm": 0, # start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate)
"rf_gain": "auto", # format_conversion="csdr convert_u8_f"
"profiles": {
"20m": { # >> Pre-recorded raw I/Q file as signal source
"name": "20m", # You will have to correctly specify: samp_rate, center_freq, format_conversion in order to correctly play an I/Q file.
"center_freq": 14150000, #start_rtl_command="(while true; do cat my_iq_file.raw; done) | csdr flowcontrol {sr} 20 ".format(sr=samp_rate*2*1.05)
"samp_rate": 384000, #format_conversion="csdr convert_u8_f"
"start_freq": 14070000,
"start_mod": "usb", #>> The rx_sdr command works with a variety of SDR harware: RTL-SDR, HackRF, SDRplay, UHD, Airspy, Red Pitaya, audio devices, etc.
}, # It will auto-detect your SDR hardware if the following tools are installed:
"30m": { # * the vendor provided driver and library,
"name": "30m", # * the vendor-specific SoapySDR wrapper library,
"center_freq": 10125000, # * and SoapySDR itself.
"samp_rate": 192000, # Check out this article on the OpenWebRX Wiki: https://github.com/simonyiszk/openwebrx/wiki/Using-rx_tools-with-OpenWebRX/
"start_freq": 10142000, #start_rtl_command="rx_sdr -F CF32 -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
"start_mod": "usb", #format_conversion=""
},
"40m": { # >> gr-osmosdr signal source using GNU Radio (follow this guide: https://github.com/simonyiszk/openwebrx/wiki/Using-GrOsmoSDR-as-signal-source)
"name": "40m", #start_rtl_command="cat /tmp/osmocom_fifo"
"center_freq": 7100000, #format_conversion=""
"samp_rate": 256000,
"start_freq": 7070000, # ==== Misc settings ====
"start_mod": "lsb",
}, shown_center_freq = center_freq #you can change this if you use an upconverter
"80m": {
"name": "80m", client_audio_buffer_size = 5
"center_freq": 3650000, #increasing client_audio_buffer_size will:
"samp_rate": 384000, # - also increase the latency
"start_freq": 3570000, # - decrease the chance of audio underruns
"start_mod": "lsb",
}, start_freq = center_freq
"49m": { start_mod = "nfm" #nfm, am, lsb, usb, cw
"name": "49m Broadcast",
"center_freq": 6050000, iq_server_port = 4951 #TCP port for ncat to listen on. It will send I/Q data over its connections, for internal use in OpenWebRX. It is only accessible from the localhost by default.
"samp_rate": 384000,
"start_freq": 6070000, #access_log = "~/openwebrx_access.log"
"start_mod": "am",
},
},
},
"sdrplay": {
"name": "SDRPlay RSP2",
"type": "sdrplay",
"ppm": 0,
"antenna": "Antenna A",
"profiles": {
"20m": {
"name": "20m",
"center_freq": 14150000,
"rf_gain": 0,
"samp_rate": 500000,
"start_freq": 14070000,
"start_mod": "usb",
},
"30m": {
"name": "30m",
"center_freq": 10125000,
"rf_gain": 0,
"samp_rate": 250000,
"start_freq": 10142000,
"start_mod": "usb",
},
"40m": {
"name": "40m",
"center_freq": 7100000,
"rf_gain": 0,
"samp_rate": 500000,
"start_freq": 7070000,
"start_mod": "lsb",
},
"80m": {
"name": "80m",
"center_freq": 3650000,
"rf_gain": 0,
"samp_rate": 500000,
"start_freq": 3570000,
"start_mod": "lsb",
},
"49m": {
"name": "49m Broadcast",
"center_freq": 6000000,
"rf_gain": 0,
"samp_rate": 500000,
"start_freq": 6070000,
"start_mod": "am",
},
},
},
}
# ==== Color themes ==== # ==== Color themes ====
# A guide is available to help you set these values: https://github.com/simonyiszk/openwebrx/wiki/Calibrating-waterfall-display-levels #A guide is available to help you set these values: https://github.com/simonyiszk/openwebrx/wiki/Calibrating-waterfall-display-levels
### default theme by teejez: ### default theme by teejez:
waterfall_colors = [0x000000FF, 0x0000FFFF, 0x00FFFFFF, 0x00FF00FF, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFFFFFFFF] waterfall_colors = "[0x000000ff,0x0000ffff,0x00ffffff,0x00ff00ff,0xffff00ff,0xff0000ff,0xff00ffff,0xffffffff]"
waterfall_min_level = -88 # in dB waterfall_min_level = -88 #in dB
waterfall_max_level = -20 waterfall_max_level = -20
waterfall_auto_level_margin = {"min": 5, "max": 40} waterfall_auto_level_margin = (5, 40)
### old theme by HA7ILM: ### old theme by HA7ILM:
# waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]" #waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]"
# waterfall_min_level = -115 #in dB #waterfall_min_level = -115 #in dB
# waterfall_max_level = 0 #waterfall_max_level = 0
# waterfall_auto_level_margin = {"min": 20, "max": 30} #waterfall_auto_level_margin = (20, 30)
##For the old colors, you might also want to set [fft_voverlap_factor] to 0. ##For the old colors, you might also want to set [fft_voverlap_factor] to 0.
# Note: When the auto waterfall level button is clicked, the following happens: #Note: When the auto waterfall level button is clicked, the following happens:
# [waterfall_min_level] = [current_min_power_level] - [waterfall_auto_level_margin["min"]] # [waterfall_min_level] = [current_min_power_level] - [waterfall_auto_level_margin[0]]
# [waterfall_max_level] = [current_max_power_level] + [waterfall_auto_level_margin["max"]] # [waterfall_max_level] = [current_max_power_level] + [waterfall_auto_level_margin[1]]
# #
# ___|________________________________________|____________________________________|________________________________________|___> signal power # ___|____________________________________|____________________________________|____________________________________|___> signal power
# \_waterfall_auto_level_margin["min"]_/ |__ current_min_power_level | \_waterfall_auto_level_margin["max"]_/ # \_waterfall_auto_level_margin[0]_/ |__ current_min_power_level | \_waterfall_auto_level_margin[1]_/
# current_max_power_level __| # current_max_power_level __|
# 3D view settings
mathbox_waterfall_frequency_resolution = 128 #bins
mathbox_waterfall_history_length = 10 #seconds
mathbox_waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]"
# === Experimental settings === # === Experimental settings ===
# Warning! The settings below are very experimental. #Warning! The settings below are very experimental.
csdr_dynamic_bufsize = False # This allows you to change the buffering mode of csdr. csdr_dynamic_bufsize = False # This allows you to change the buffering mode of csdr.
csdr_print_bufsizes = False # This prints the buffer sizes used for csdr processes. csdr_print_bufsizes = False # This prints the buffer sizes used for csdr processes.
csdr_through = False # Setting this True will print out how much data is going into the DSP chains. csdr_through = False # Setting this True will print out how much data is going into the DSP chains.
nmux_memory = 50 # in megabytes. This sets the approximate size of the circular buffer used by nmux. nmux_memory = 50 #in megabytes. This sets the approximate size of the circular buffer used by nmux.
google_maps_api_key = "" #Look up external IP address automatically from icanhazip.com, and use it as [server_hostname]
"""
# how long should positions be visible on the map? print "[openwebrx-config] Detecting external IP address..."
# they will start fading out after half of that import urllib2
# in seconds; default: 2 hours server_hostname=urllib2.urlopen("http://icanhazip.com").read()[:-1]
map_position_retention_time = 2 * 60 * 60 print "[openwebrx-config] External IP address detected:", server_hostname
"""
# decoder queue configuration
# due to the nature of some operating modes (ft8, ft8, jt9, jt65, wspr and js8), the data is recorded for a given amount
# of time (6 seconds up to 2 minutes) and decoded at the end. this can lead to very high peak loads.
# to mitigate this, the recordings will be queued and processed in sequence.
# the number of workers will limit the total amount of work (one worker will losely occupy one cpu / thread)
decoding_queue_workers = 2
# the maximum queue length will cause decodes to be dumped if the workers cannot keep up
# if you are running background services, make sure this number is high enough to accept the task influx during peaks
# i.e. this should be higher than the number of decoding services running at the same time
decoding_queue_length = 10
# wsjt decoding depth will allow more results, but will also consume more cpu
wsjt_decoding_depth = 3
# can also be set for each mode separately
# jt65 seems to be somewhat prone to erroneous decodes, this setting handles that to some extent
wsjt_decoding_depths = {"jt65": 1}
# JS8 comes in different speeds: normal, slow, fast, turbo. This setting controls which ones are enabled.
js8_enabled_profiles = ["normal", "slow"]
# JS8 decoding depth; higher value will get more results, but will also consume more cpu
js8_decoding_depth = 3
temporary_directory = "/tmp"
services_enabled = False
services_decoders = ["ft8", "ft4", "wspr", "packet"]
# === aprs igate settings ===
# if you want to share your APRS decodes with the aprs network, configure these settings accordingly
aprs_callsign = "N0CALL"
aprs_igate_enabled = False
aprs_igate_server = "euro.aprs2.net"
aprs_igate_password = ""
# beacon uses the receiver_gps setting, so if you enable this, make sure the location is correct there
aprs_igate_beacon = False
# path to the aprs symbols repository (get it here: https://github.com/hessu/aprs-symbols)
aprs_symbols_path = "/opt/aprs-symbols/png"
# === PSK Reporter setting ===
# enable this if you want to upload all ft8, ft4 etc spots to pskreporter.info
# this also uses the receiver_gps setting from above, so make sure it contains a correct locator
pskreporter_enabled = False
pskreporter_callsign = "N0CALL"
# === Web admin settings ===
# this feature is experimental at the moment. it should not be enabled on shared receivers since it allows remote
# changes to the receiver settings. enable for testing in controlled environment only.
# webadmin_enabled = False

424
csdr.py Executable file
View File

@ -0,0 +1,424 @@
"""
OpenWebRX csdr plugin: do the signal processing with csdr
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import subprocess
import time
import os
import code
import signal
import fcntl
class dsp:
def __init__(self):
self.samp_rate = 250000
self.output_rate = 11025 #this is default, and cannot be set at the moment
self.fft_size = 1024
self.fft_fps = 5
self.offset_freq = 0
self.low_cut = -4000
self.high_cut = 4000
self.bpf_transition_bw = 320 #Hz, and this is a constant
self.ddc_transition_bw_rate = 0.15 # of the IF sample rate
self.running = False
self.secondary_processes_running = False
self.audio_compression = "none"
self.fft_compression = "none"
self.demodulator = "nfm"
self.name = "csdr"
self.format_conversion = "csdr convert_u8_f"
self.base_bufsize = 512
self.nc_port = 4951
self.csdr_dynamic_bufsize = False
self.csdr_print_bufsizes = False
self.csdr_through = False
self.squelch_level = 0
self.fft_averages = 50
self.iqtee = False
self.iqtee2 = False
self.secondary_demodulator = None
self.secondary_fft_size = 1024
self.secondary_process_fft = None
self.secondary_process_demod = None
self.pipe_names=["bpf_pipe", "shift_pipe", "squelch_pipe", "smeter_pipe", "iqtee_pipe", "iqtee2_pipe"]
self.secondary_pipe_names=["secondary_shift_pipe"]
self.secondary_offset_freq = 1000
def chain(self,which):
any_chain_base="nc -v 127.0.0.1 {nc_port} | "
if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | "
if self.csdr_through: any_chain_base+="csdr through | "
any_chain_base+=self.format_conversion+(" | " if self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | "
if which == "fft":
fft_chain_base = any_chain_base+"csdr fft_cc {fft_size} {fft_block_size} | " + \
("csdr logpower_cf -70 | " if self.fft_averages == 0 else "csdr logaveragepower_cf -70 {fft_size} {fft_averages} | ") + \
"csdr fft_exchange_sides_ff {fft_size}"
if self.fft_compression=="adpcm":
return fft_chain_base+" | csdr compress_fft_adpcm_f_u8 {fft_size}"
else:
return fft_chain_base
chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | "
if self.secondary_demodulator:
chain_begin+="csdr tee {iqtee_pipe} | "
chain_begin+="csdr tee {iqtee2_pipe} | "
chain_end = ""
if self.audio_compression=="adpcm":
chain_end = " | csdr encode_ima_adpcm_i16_u8"
if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr fastagc_ff 1024 | csdr convert_f_s16"+chain_end
elif which == "am": return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
def secondary_chain(self, which):
secondary_chain_base="cat {input_pipe} | "
if which == "fft":
return secondary_chain_base+"csdr realpart_cf | csdr fft_fc {secondary_fft_input_size} {secondary_fft_block_size} | csdr logpower_cf -70 " + (" | csdr compress_fft_adpcm_f_u8 {secondary_fft_size}" if self.fft_compression=="adpcm" else "")
elif which == "bpsk31":
return secondary_chain_base + "csdr shift_addition_cc --fifo {secondary_shift_pipe} | " + \
"csdr bandpass_fir_fft_cc $(csdr '=-(31.25)/{if_samp_rate}') $(csdr '=(31.25)/{if_samp_rate}') $(csdr '=31.25/{if_samp_rate}') | " + \
"csdr simple_agc_cc 0.001 0.5 | " + \
"csdr timing_recovery_cc GARDNER {secondary_samples_per_bits} 0.5 2 --add_q | " + \
"CSDR_FIXED_BUFSIZE=1 csdr dbpsk_decoder_c_u8 | " + \
"CSDR_FIXED_BUFSIZE=1 csdr psk31_varicode_decoder_u8_u8"
def set_secondary_demodulator(self, what):
self.secondary_demodulator = what
def secondary_fft_block_size(self):
return (self.samp_rate/self.decimation)/(self.fft_fps*2) #*2 is there because we do FFT on real signal here
def secondary_decimation(self):
return 1 #currently unused
def secondary_bpf_cutoff(self):
if self.secondary_demodulator == "bpsk31":
return (31.25/2) / self.if_samp_rate()
return 0
def secondary_bpf_transition_bw(self):
if self.secondary_demodulator == "bpsk31":
return (31.25/2) / self.if_samp_rate()
return 0
def secondary_samples_per_bits(self):
if self.secondary_demodulator == "bpsk31":
return int(round(self.if_samp_rate()/31.25))&~3
return 0
def secondary_bw(self):
if self.secondary_demodulator == "bpsk31":
return 31.25
def start_secondary_demodulator(self):
if(not self.secondary_demodulator): return
print "[openwebrx] starting secondary demodulator from IF input sampled at %d"%self.if_samp_rate()
secondary_command_fft=self.secondary_chain("fft")
secondary_command_demod=self.secondary_chain(self.secondary_demodulator)
self.try_create_pipes(self.secondary_pipe_names, secondary_command_demod + secondary_command_fft)
secondary_command_fft=secondary_command_fft.format( \
input_pipe=self.iqtee_pipe, \
secondary_fft_input_size=self.secondary_fft_size, \
secondary_fft_size=self.secondary_fft_size, \
secondary_fft_block_size=self.secondary_fft_block_size(), \
)
secondary_command_demod=secondary_command_demod.format( \
input_pipe=self.iqtee2_pipe, \
secondary_shift_pipe=self.secondary_shift_pipe, \
secondary_decimation=self.secondary_decimation(), \
secondary_samples_per_bits=self.secondary_samples_per_bits(), \
secondary_bpf_cutoff=self.secondary_bpf_cutoff(), \
secondary_bpf_transition_bw=self.secondary_bpf_transition_bw(), \
if_samp_rate=self.if_samp_rate()
)
print "[openwebrx-dsp-plugin:csdr] secondary command (fft) =", secondary_command_fft
print "[openwebrx-dsp-plugin:csdr] secondary command (demod) =", secondary_command_demod
#code.interact(local=locals())
my_env=os.environ.copy()
#if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
self.secondary_process_fft = subprocess.Popen(secondary_command_fft, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
print "[openwebrx-dsp-plugin:csdr] Popen on secondary command (fft)"
self.secondary_process_demod = subprocess.Popen(secondary_command_demod, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env) #TODO digimodes
print "[openwebrx-dsp-plugin:csdr] Popen on secondary command (demod)" #TODO digimodes
self.secondary_processes_running = True
#open control pipes for csdr and send initialization data
# print "==========> 1"
if self.secondary_shift_pipe != None: #TODO digimodes
# print "==========> 2", self.secondary_shift_pipe
self.secondary_shift_pipe_file=open(self.secondary_shift_pipe,"w") #TODO digimodes
# print "==========> 3"
self.set_secondary_offset_freq(self.secondary_offset_freq) #TODO digimodes
# print "==========> 4"
self.set_pipe_nonblocking(self.secondary_process_demod.stdout)
self.set_pipe_nonblocking(self.secondary_process_fft.stdout)
def set_secondary_offset_freq(self, value):
self.secondary_offset_freq=value
if self.secondary_processes_running:
self.secondary_shift_pipe_file.write("%g\n"%(-float(self.secondary_offset_freq)/self.if_samp_rate()))
self.secondary_shift_pipe_file.flush()
def stop_secondary_demodulator(self):
if self.secondary_processes_running == False: return
self.try_delete_pipes(self.secondary_pipe_names)
if self.secondary_process_fft: os.killpg(os.getpgid(self.secondary_process_fft.pid), signal.SIGTERM)
if self.secondary_process_demod: os.killpg(os.getpgid(self.secondary_process_demod.pid), signal.SIGTERM)
self.secondary_processes_running = False
def read_secondary_demod(self, size):
return self.secondary_process_demod.stdout.read(size)
def read_secondary_fft(self, size):
return self.secondary_process_fft.stdout.read(size)
def get_secondary_demodulator(self):
return self.secondary_demodulator
def set_secondary_fft_size(self,secondary_fft_size):
#to change this, restart is required
self.secondary_fft_size=secondary_fft_size
def set_audio_compression(self,what):
self.audio_compression = what
def set_fft_compression(self,what):
self.fft_compression = what
def get_fft_bytes_to_read(self):
if self.fft_compression=="none": return self.fft_size*4
if self.fft_compression=="adpcm": return (self.fft_size/2)+(10/2)
def get_secondary_fft_bytes_to_read(self):
if self.fft_compression=="none": return self.secondary_fft_size*4
if self.fft_compression=="adpcm": return (self.secondary_fft_size/2)+(10/2)
def set_samp_rate(self,samp_rate):
#to change this, restart is required
self.samp_rate=samp_rate
self.decimation=1
while self.samp_rate/(self.decimation+1)>self.output_rate:
self.decimation+=1
self.last_decimation=float(self.if_samp_rate())/self.output_rate
def if_samp_rate(self):
return self.samp_rate/self.decimation
def get_name(self):
return self.name
def get_output_rate(self):
return self.output_rate
def set_output_rate(self,output_rate):
self.output_rate=output_rate
self.set_samp_rate(self.samp_rate) #as it depends on output_rate
def set_demodulator(self,demodulator):
#to change this, restart is required
self.demodulator=demodulator
def get_demodulator(self):
return self.demodulator
def set_fft_size(self,fft_size):
#to change this, restart is required
self.fft_size=fft_size
def set_fft_fps(self,fft_fps):
#to change this, restart is required
self.fft_fps=fft_fps
def set_fft_averages(self,fft_averages):
#to change this, restart is required
self.fft_averages=fft_averages
def fft_block_size(self):
if self.fft_averages == 0: return self.samp_rate/self.fft_fps
else: return self.samp_rate/self.fft_fps/self.fft_averages
def set_format_conversion(self,format_conversion):
self.format_conversion=format_conversion
def set_offset_freq(self,offset_freq):
self.offset_freq=offset_freq
if self.running:
self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate))
self.shift_pipe_file.flush()
def set_bpf(self,low_cut,high_cut):
self.low_cut=low_cut
self.high_cut=high_cut
if self.running:
self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) )
self.bpf_pipe_file.flush()
def get_bpf(self):
return [self.low_cut, self.high_cut]
def set_squelch_level(self, squelch_level):
self.squelch_level=squelch_level
if self.running:
self.squelch_pipe_file.write( "%g\n"%(float(self.squelch_level)) )
self.squelch_pipe_file.flush()
def get_smeter_level(self):
if self.running:
line=self.smeter_pipe_file.readline()
return float(line[:-1])
def mkfifo(self,path):
try:
os.unlink(path)
except:
pass
os.mkfifo(path)
def ddc_transition_bw(self):
return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate))
def try_create_pipes(self, pipe_names, command_base):
# print "try_create_pipes"
for pipe_name in pipe_names:
# print "\t"+pipe_name
if "{"+pipe_name+"}" in command_base:
setattr(self, pipe_name, self.pipe_base_path+pipe_name)
self.mkfifo(getattr(self, pipe_name))
else:
setattr(self, pipe_name, None)
def try_delete_pipes(self, pipe_names):
for pipe_name in pipe_names:
pipe_path = getattr(self,pipe_name,None)
if pipe_path:
try: os.unlink(pipe_path)
except Exception as e: print "[openwebrx-dsp-plugin:csdr] try_delete_pipes() ::", e
def set_pipe_nonblocking(self, pipe):
flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
fcntl.fcntl(pipe, fcntl.F_SETFL, flags | os.O_NONBLOCK)
def start(self):
command_base=self.chain(self.demodulator)
#create control pipes for csdr
self.pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self))
# self.bpf_pipe = self.shift_pipe = self.squelch_pipe = self.smeter_pipe = None
self.try_create_pipes(self.pipe_names, command_base)
# if "{bpf_pipe}" in command_base:
# self.bpf_pipe=pipe_base_path+"bpf"
# self.mkfifo(self.bpf_pipe)
# if "{shift_pipe}" in command_base:
# self.shift_pipe=pipe_base_path+"shift"
# self.mkfifo(self.shift_pipe)
# if "{squelch_pipe}" in command_base:
# self.squelch_pipe=pipe_base_path+"squelch"
# self.mkfifo(self.squelch_pipe)
# if "{smeter_pipe}" in command_base:
# self.smeter_pipe=pipe_base_path+"smeter"
# self.mkfifo(self.smeter_pipe)
# if "{iqtee_pipe}" in command_base:
# self.iqtee_pipe=pipe_base_path+"iqtee"
# self.mkfifo(self.iqtee_pipe)
# if "{iqtee2_pipe}" in command_base:
# self.iqtee2_pipe=pipe_base_path+"iqtee2"
# self.mkfifo(self.iqtee2_pipe)
#run the command
command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_pipe, decimation=self.decimation, \
last_decimation=self.last_decimation, fft_size=self.fft_size, fft_block_size=self.fft_block_size(), fft_averages=self.fft_averages, \
bpf_transition_bw=float(self.bpf_transition_bw)/self.if_samp_rate(), ddc_transition_bw=self.ddc_transition_bw(), \
flowcontrol=int(self.samp_rate*2), start_bufsize=self.base_bufsize*self.decimation, nc_port=self.nc_port, \
squelch_pipe=self.squelch_pipe, smeter_pipe=self.smeter_pipe, iqtee_pipe=self.iqtee_pipe, iqtee2_pipe=self.iqtee2_pipe )
print "[openwebrx-dsp-plugin:csdr] Command =",command
#code.interact(local=locals())
my_env=os.environ.copy()
if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
self.running = True
#open control pipes for csdr and send initialization data
if self.bpf_pipe != None:
self.bpf_pipe_file=open(self.bpf_pipe,"w")
self.set_bpf(self.low_cut,self.high_cut)
if self.shift_pipe != None:
self.shift_pipe_file=open(self.shift_pipe,"w")
self.set_offset_freq(self.offset_freq)
if self.squelch_pipe != None:
self.squelch_pipe_file=open(self.squelch_pipe,"w")
self.set_squelch_level(self.squelch_level)
if self.smeter_pipe != None:
self.smeter_pipe_file=open(self.smeter_pipe,"r")
self.set_pipe_nonblocking(self.smeter_pipe_file)
self.start_secondary_demodulator()
def read(self,size):
return self.process.stdout.read(size)
def stop(self):
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
self.stop_secondary_demodulator()
#if(self.process.poll()!=None):return # returns None while subprocess is running
#while(self.process.poll()==None):
# #self.process.kill()
# print "killproc",os.getpgid(self.process.pid),self.process.pid
# os.killpg(self.process.pid, signal.SIGTERM)
#
# time.sleep(0.1)
self.try_delete_pipes(self.pipe_names)
# if self.bpf_pipe:
# try: os.unlink(self.bpf_pipe)
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
# if self.shift_pipe:
# try: os.unlink(self.shift_pipe)
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.shift_pipe
# if self.squelch_pipe:
# try: os.unlink(self.squelch_pipe)
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.squelch_pipe
# if self.smeter_pipe:
# try: os.unlink(self.smeter_pipe)
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.smeter_pipe
# if self.iqtee_pipe:
# try: os.unlink(self.iqtee_pipe)
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.iqtee_pipe
# if self.iqtee2_pipe:
# try: os.unlink(self.iqtee2_pipe)
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.iqtee2_pipe
self.running = False
def restart(self):
self.stop()
self.start()
def __del__(self):
self.stop()
del(self.process)

View File

@ -1,853 +0,0 @@
"""
OpenWebRX csdr plugin: do the signal processing with csdr
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
Copyright (c) 2019-2020 by Jakob Ketterl <dd5jfk@darc.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import subprocess
import os
import signal
import threading
import math
from functools import partial
from owrx.kiss import KissClient, DirewolfConfig
from owrx.wsjt import Ft8Profile, WsprProfile, Jt9Profile, Jt65Profile, Ft4Profile
from owrx.js8 import Js8Profiles
from owrx.audio import AudioChopper
import logging
logger = logging.getLogger(__name__)
class output(object):
def send_output(self, t, read_fn):
if not self.supports_type(t):
# TODO rewrite the output mechanism in a way that avoids producing unnecessary data
logger.warning("dumping output of type %s since it is not supported.", t)
threading.Thread(target=self.pump(read_fn, lambda x: None)).start()
return
self.receive_output(t, read_fn)
def receive_output(self, t, read_fn):
pass
def pump(self, read, write):
def copy():
run = True
while run:
data = read()
if data is None or (isinstance(data, bytes) and len(data) == 0):
run = False
else:
write(data)
return copy
def supports_type(self, t):
return True
class Pipe(object):
READ = "r"
WRITE = "w"
NONE = None
@staticmethod
def create(path, t, encoding=None):
if t == Pipe.READ:
return ReadingPipe(path, encoding=encoding)
elif t == Pipe.WRITE:
return WritingPipe(path, encoding=encoding)
elif t == Pipe.NONE:
return Pipe(path, None, encoding=encoding)
def __init__(self, path, direction, encoding=None):
self.path = path
self.direction = direction
self.encoding = encoding
self.file = None
try:
os.unlink(path)
except Exception:
pass
os.mkfifo(path)
def open(self):
self.file = open(self.path, self.direction, encoding=self.encoding)
def close(self):
if self.file is None:
return
try:
self.file.close()
os.unlink(self.path)
except FileNotFoundError:
# it seems like we keep calling this twice. no idea why, but we don't need the resulting error.
pass
except Exception:
logger.exception("Pipe.close()")
def __str__(self):
return self.path
class WritingPipe(Pipe):
def __init__(self, path, encoding=None):
self.queue = []
self.queueLock = threading.Lock()
super().__init__(path, "w", encoding=encoding)
self.open()
def open_and_dequeue(self):
super().open()
with self.queueLock:
for i in self.queue:
self.file.write(i)
self.file.flush()
self.queue = None
def open(self):
threading.Thread(target=self.open_and_dequeue).start()
def write(self, data):
if self.file is None:
with self.queueLock:
self.queue.append(data)
return
r = self.file.write(data)
self.file.flush()
return r
def close(self):
if self.file is None:
logger.warning("queue %s never successfully opened - thread leak!", self.path)
super().close()
class ReadingPipe(Pipe):
def __init__(self, path, encoding=None):
super().__init__(path, "r", encoding=encoding)
def read(self):
if self.file is None:
self.open()
return self.file.read()
def readline(self):
if self.file is None:
self.open()
return self.file.readline()
class dsp(object):
def __init__(self, output):
self.samp_rate = 250000
self.output_rate = 11025
self.fft_size = 1024
self.fft_fps = 5
self.center_freq = 0
self.offset_freq = 0
self.low_cut = -4000
self.high_cut = 4000
self.bpf_transition_bw = 320 # Hz, and this is a constant
self.ddc_transition_bw_rate = 0.15 # of the IF sample rate
self.running = False
self.secondary_processes_running = False
self.audio_compression = "none"
self.fft_compression = "none"
self.demodulator = "nfm"
self.name = "csdr"
self.base_bufsize = 512
self.decimation = None
self.last_decimation = None
self.nc_port = None
self.csdr_dynamic_bufsize = False
self.csdr_print_bufsizes = False
self.csdr_through = False
self.squelch_level = -150
self.fft_averages = 50
self.iqtee = False
self.iqtee2 = False
self.secondary_demodulator = None
self.secondary_fft_size = 1024
self.secondary_process_fft = None
self.secondary_process_demod = None
self.pipe_names = {
"bpf_pipe": Pipe.WRITE,
"shift_pipe": Pipe.WRITE,
"squelch_pipe": Pipe.WRITE,
"smeter_pipe": Pipe.READ,
"meta_pipe": Pipe.READ,
"iqtee_pipe": Pipe.NONE,
"iqtee2_pipe": Pipe.NONE,
"dmr_control_pipe": Pipe.WRITE,
}
self.pipes = {}
self.secondary_pipe_names = {"secondary_shift_pipe": Pipe.WRITE}
self.secondary_offset_freq = 1000
self.unvoiced_quality = 1
self.modification_lock = threading.Lock()
self.output = output
self.temporary_directory = None
self.pipe_base_path = None
self.set_temporary_directory("/tmp")
self.is_service = False
self.direwolf_config = None
self.direwolf_port = None
self.process = None
def set_service(self, flag=True):
self.is_service = flag
def set_temporary_directory(self, what):
self.temporary_directory = what
self.pipe_base_path = "{tmp_dir}/openwebrx_pipe_{myid}_".format(tmp_dir=self.temporary_directory, myid=id(self))
def chain(self, which):
chain = ["nc -v 127.0.0.1 {nc_port}"]
if self.csdr_dynamic_bufsize:
chain += ["csdr setbuf {start_bufsize}"]
if self.csdr_through:
chain += ["csdr through"]
if which == "fft":
chain += [
"csdr fft_cc {fft_size} {fft_block_size}",
"csdr logpower_cf -70"
if self.fft_averages == 0
else "csdr logaveragepower_cf -70 {fft_size} {fft_averages}",
"csdr fft_exchange_sides_ff {fft_size}",
]
if self.fft_compression == "adpcm":
chain += ["csdr compress_fft_adpcm_f_u8 {fft_size}"]
return chain
chain += ["csdr shift_addfast_cc --fifo {shift_pipe}"]
if self.decimation > 1:
chain += ["csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING"]
chain += ["csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING"]
if self.output.supports_type("smeter"):
chain += [
"csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 {smeter_report_every}"
]
if self.secondary_demodulator:
if self.output.supports_type("secondary_fft"):
chain += ["csdr tee {iqtee_pipe}"]
chain += ["csdr tee {iqtee2_pipe}"]
# early exit if we don't want audio
if not self.output.supports_type("audio"):
return chain
# safe some cpu cycles... no need to decimate if decimation factor is 1
last_decimation_block = (
["csdr fractional_decimator_ff {last_decimation}"] if self.last_decimation != 1.0 else []
)
if which == "nfm":
chain += ["csdr fmdemod_quadri_cf", "csdr limit_ff"]
chain += last_decimation_block
chain += ["csdr deemphasis_nfm_ff {audio_rate}"]
if self.get_audio_rate() != self.get_output_rate():
chain += [
"sox -t raw -r {audio_rate} -e floating-point -b 32 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - "
]
else:
chain += ["csdr convert_f_s16"]
elif self.isDigitalVoice(which):
chain += ["csdr fmdemod_quadri_cf", "dc_block "]
chain += last_decimation_block
# dsd modes
if which in ["dstar", "nxdn"]:
chain += ["csdr limit_ff", "csdr convert_f_s16"]
if which == "dstar":
chain += ["dsd -fd -i - -o - -u {unvoiced_quality} -g -1 "]
elif which == "nxdn":
chain += ["dsd -fi -i - -o - -u {unvoiced_quality} -g -1 "]
chain += ["CSDR_FIXED_BUFSIZE=32 csdr convert_s16_f"]
max_gain = 5
# digiham modes
else:
chain += ["rrc_filter", "gfsk_demodulator"]
if which == "dmr":
chain += [
"dmr_decoder --fifo {meta_pipe} --control-fifo {dmr_control_pipe}",
"mbe_synthesizer -f -u {unvoiced_quality}",
]
elif which == "ysf":
chain += ["ysf_decoder --fifo {meta_pipe}", "mbe_synthesizer -y -f -u {unvoiced_quality}"]
max_gain = 0.0005
chain += [
"digitalvoice_filter -f",
"CSDR_FIXED_BUFSIZE=32 csdr agc_ff 160000 0.8 1 0.0000001 {max_gain}".format(max_gain=max_gain),
"sox -t raw -r 8000 -e floating-point -b 32 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
elif which == "am":
chain += ["csdr amdemod_cf", "csdr fastdcblock_ff"]
chain += last_decimation_block
chain += ["csdr agc_ff", "csdr limit_ff", "csdr convert_f_s16"]
elif which == "ssb":
chain += ["csdr realpart_cf"]
chain += last_decimation_block
chain += ["csdr agc_ff", "csdr limit_ff"]
# fixed sample rate necessary for the wsjt-x tools. fix with sox...
if self.get_audio_rate() != self.get_output_rate():
chain += [
"sox -t raw -r {audio_rate} -e floating-point -b 32 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - "
]
else:
chain += ["csdr convert_f_s16"]
if self.audio_compression == "adpcm":
chain += ["csdr encode_ima_adpcm_i16_u8"]
return chain
def secondary_chain(self, which):
chain = ["cat {input_pipe}"]
if which == "fft":
chain += [
"csdr realpart_cf",
"csdr fft_fc {secondary_fft_input_size} {secondary_fft_block_size}",
"csdr logpower_cf -70",
]
if self.fft_compression == "adpcm":
chain += ["csdr compress_fft_adpcm_f_u8 {secondary_fft_size}"]
return chain
elif which == "bpsk31" or which == "bpsk63":
return chain + [
"csdr shift_addfast_cc --fifo {secondary_shift_pipe}",
"csdr bandpass_fir_fft_cc -{secondary_bpf_cutoff} {secondary_bpf_cutoff} {secondary_bpf_cutoff}",
"csdr simple_agc_cc 0.001 0.5",
"csdr timing_recovery_cc GARDNER {secondary_samples_per_bits} 0.5 2 --add_q",
"CSDR_FIXED_BUFSIZE=1 csdr dbpsk_decoder_c_u8",
"CSDR_FIXED_BUFSIZE=1 csdr psk31_varicode_decoder_u8_u8",
]
elif self.isWsjtMode(which) or self.isJs8(which):
chain += ["csdr realpart_cf"]
if self.last_decimation != 1.0:
chain += ["csdr fractional_decimator_ff {last_decimation}"]
return chain + ["csdr limit_ff", "csdr convert_f_s16"]
elif which == "packet":
chain += ["csdr fmdemod_quadri_cf"]
if self.last_decimation != 1.0:
chain += ["csdr fractional_decimator_ff {last_decimation}"]
return chain + ["csdr convert_f_s16", "direwolf -c {direwolf_config} -r {audio_rate} -t 0 -q d -q h 1>&2"]
elif which == "pocsag":
chain += ["csdr fmdemod_quadri_cf"]
if self.last_decimation != 1.0:
chain += ["csdr fractional_decimator_ff {last_decimation}"]
return chain + ["fsk_demodulator -i", "pocsag_decoder"]
def set_secondary_demodulator(self, what):
if self.get_secondary_demodulator() == what:
return
self.secondary_demodulator = what
self.calculate_decimation()
self.restart()
def secondary_fft_block_size(self):
return (self.samp_rate / self.decimation) / (
self.fft_fps * 2
) # *2 is there because we do FFT on real signal here
def secondary_decimation(self):
return 1 # currently unused
def secondary_bpf_cutoff(self):
if self.secondary_demodulator == "bpsk31":
return 31.25 / self.if_samp_rate()
elif self.secondary_demodulator == "bpsk63":
return 62.5 / self.if_samp_rate()
return 0
def secondary_bpf_transition_bw(self):
if self.secondary_demodulator == "bpsk31":
return 31.25 / self.if_samp_rate()
elif self.secondary_demodulator == "bpsk63":
return 62.5 / self.if_samp_rate()
return 0
def secondary_samples_per_bits(self):
if self.secondary_demodulator == "bpsk31":
return int(round(self.if_samp_rate() / 31.25)) & ~3
elif self.secondary_demodulator == "bpsk63":
return int(round(self.if_samp_rate() / 62.5)) & ~3
return 0
def secondary_bw(self):
if self.secondary_demodulator == "bpsk31":
return 31.25
elif self.secondary_demodulator == "bpsk63":
return 62.5
def start_secondary_demodulator(self):
if not self.secondary_demodulator:
return
logger.debug("starting secondary demodulator from IF input sampled at %d" % self.if_samp_rate())
secondary_command_demod = " | ".join(self.secondary_chain(self.secondary_demodulator))
self.try_create_pipes(self.secondary_pipe_names, secondary_command_demod)
self.try_create_configs(secondary_command_demod)
secondary_command_demod = secondary_command_demod.format(
input_pipe=self.pipes["iqtee2_pipe"],
secondary_shift_pipe=self.pipes["secondary_shift_pipe"],
secondary_decimation=self.secondary_decimation(),
secondary_samples_per_bits=self.secondary_samples_per_bits(),
secondary_bpf_cutoff=self.secondary_bpf_cutoff(),
secondary_bpf_transition_bw=self.secondary_bpf_transition_bw(),
if_samp_rate=self.if_samp_rate(),
last_decimation=self.last_decimation,
audio_rate=self.get_audio_rate(),
direwolf_config=self.direwolf_config,
)
logger.debug("secondary command (demod) = %s", secondary_command_demod)
my_env = os.environ.copy()
# if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
if self.csdr_print_bufsizes:
my_env["CSDR_PRINT_BUFSIZES"] = "1"
if self.output.supports_type("secondary_fft"):
secondary_command_fft = " | ".join(self.secondary_chain("fft"))
secondary_command_fft = secondary_command_fft.format(
input_pipe=self.pipes["iqtee_pipe"],
secondary_fft_input_size=self.secondary_fft_size,
secondary_fft_size=self.secondary_fft_size,
secondary_fft_block_size=self.secondary_fft_block_size(),
)
logger.debug("secondary command (fft) = %s", secondary_command_fft)
self.secondary_process_fft = subprocess.Popen(
secondary_command_fft, stdout=subprocess.PIPE, shell=True, start_new_session=True, env=my_env
)
self.output.send_output(
"secondary_fft",
partial(self.secondary_process_fft.stdout.read, int(self.get_secondary_fft_bytes_to_read())),
)
# direwolf does not provide any meaningful data on stdout
# more specifically, it doesn't provide any data. if however, for any strange reason, it would start to do so,
# it would block if not read. by piping it to devnull, we avoid a potential pitfall here.
secondary_output = subprocess.DEVNULL if self.isPacket() else subprocess.PIPE
self.secondary_process_demod = subprocess.Popen(
secondary_command_demod, stdout=secondary_output, shell=True, start_new_session=True, env=my_env
)
self.secondary_processes_running = True
if self.isWsjtMode():
smd = self.get_secondary_demodulator()
chopper_profile = None
if smd == "ft8":
chopper_profile = Ft8Profile()
elif smd == "wspr":
chopper_profile = WsprProfile()
elif smd == "jt65":
chopper_profile = Jt65Profile()
elif smd == "jt9":
chopper_profile = Jt9Profile()
elif smd == "ft4":
chopper_profile = Ft4Profile()
if chopper_profile is not None:
chopper = AudioChopper(self, self.secondary_process_demod.stdout, chopper_profile)
chopper.start()
self.output.send_output("wsjt_demod", chopper.read)
elif self.isJs8():
chopper = AudioChopper(self, self.secondary_process_demod.stdout, *Js8Profiles.getEnabledProfiles())
chopper.start()
self.output.send_output("js8_demod", chopper.read)
elif self.isPacket():
# we best get the ax25 packets from the kiss socket
kiss = KissClient(self.direwolf_port)
self.output.send_output("packet_demod", kiss.read)
elif self.isPocsag():
self.output.send_output("pocsag_demod", self.secondary_process_demod.stdout.readline)
else:
self.output.send_output("secondary_demod", partial(self.secondary_process_demod.stdout.read, 1))
# open control pipes for csdr and send initialization data
if self.has_pipe("secondary_shift_pipe"): # TODO digimodes
self.set_secondary_offset_freq(self.secondary_offset_freq) # TODO digimodes
def set_secondary_offset_freq(self, value):
self.secondary_offset_freq = value
if self.secondary_processes_running and self.has_pipe("secondary_shift_pipe"):
self.pipes["secondary_shift_pipe"].write("%g\n" % (-float(self.secondary_offset_freq) / self.if_samp_rate()))
def stop_secondary_demodulator(self):
if not self.secondary_processes_running:
return
self.try_delete_pipes(self.secondary_pipe_names)
self.try_delete_configs()
if self.secondary_process_fft:
try:
os.killpg(os.getpgid(self.secondary_process_fft.pid), signal.SIGTERM)
except ProcessLookupError:
# been killed by something else, ignore
pass
if self.secondary_process_demod:
try:
os.killpg(os.getpgid(self.secondary_process_demod.pid), signal.SIGTERM)
except ProcessLookupError:
# been killed by something else, ignore
pass
self.secondary_processes_running = False
def get_secondary_demodulator(self):
return self.secondary_demodulator
def set_secondary_fft_size(self, secondary_fft_size):
# to change this, restart is required
self.secondary_fft_size = secondary_fft_size
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
def get_fft_bytes_to_read(self):
if self.fft_compression == "none":
return self.fft_size * 4
if self.fft_compression == "adpcm":
return int((self.fft_size / 2) + (10 / 2))
def get_secondary_fft_bytes_to_read(self):
if self.fft_compression == "none":
return self.secondary_fft_size * 4
if self.fft_compression == "adpcm":
return (self.secondary_fft_size / 2) + (10 / 2)
def set_samp_rate(self, samp_rate):
self.samp_rate = samp_rate
self.calculate_decimation()
if self.running:
self.restart()
def calculate_decimation(self):
(self.decimation, self.last_decimation, _) = self.get_decimation(self.samp_rate, self.get_audio_rate())
def get_decimation(self, input_rate, output_rate):
decimation = 1
while input_rate / (decimation + 1) >= output_rate:
decimation += 1
fraction = float(input_rate / decimation) / output_rate
intermediate_rate = input_rate / decimation
return decimation, fraction, intermediate_rate
def if_samp_rate(self):
return self.samp_rate / self.decimation
def get_name(self):
return self.name
def get_output_rate(self):
return self.output_rate
def get_audio_rate(self):
if self.isDigitalVoice() or self.isPacket() or self.isPocsag():
return 48000
elif self.isWsjtMode() or self.isJs8():
return 12000
return self.get_output_rate()
def isDigitalVoice(self, demodulator=None):
if demodulator is None:
demodulator = self.get_demodulator()
return demodulator in ["dmr", "dstar", "nxdn", "ysf"]
def isWsjtMode(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator in ["ft8", "wspr", "jt65", "jt9", "ft4"]
def isJs8(self, demodulator = None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator == "js8"
def isPacket(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator == "packet"
def isPocsag(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator == "pocsag"
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 demodulator in ["usb", "lsb", "cw"]:
demodulator = "ssb"
if self.demodulator == demodulator:
return
self.demodulator = demodulator
self.calculate_decimation()
self.restart()
def get_demodulator(self):
return self.demodulator
def set_fft_size(self, fft_size):
self.fft_size = fft_size
self.restart()
def set_fft_fps(self, fft_fps):
self.fft_fps = fft_fps
self.restart()
def set_fft_averages(self, fft_averages):
self.fft_averages = fft_averages
self.restart()
def fft_block_size(self):
if self.fft_averages == 0:
return self.samp_rate / self.fft_fps
else:
return self.samp_rate / self.fft_fps / self.fft_averages
def set_offset_freq(self, offset_freq):
self.offset_freq = offset_freq
if self.running:
self.pipes["shift_pipe"].write("%g\n" % (-float(self.offset_freq) / self.samp_rate))
def set_center_freq(self, center_freq):
# dsp only needs to know this to be able to pass it to decoders in the form of get_operating_freq()
self.center_freq = center_freq
def get_operating_freq(self):
return self.center_freq + self.offset_freq
def set_bpf(self, low_cut, high_cut):
self.low_cut = low_cut
self.high_cut = high_cut
if self.running:
self.pipes["bpf_pipe"].write(
"%g %g\n" % (float(self.low_cut) / self.if_samp_rate(), float(self.high_cut) / self.if_samp_rate())
)
def get_bpf(self):
return [self.low_cut, self.high_cut]
def convertToLinear(self, db):
return float(math.pow(10, db / 10))
def set_squelch_level(self, squelch_level):
self.squelch_level = squelch_level
# no squelch required on digital voice modes
actual_squelch = -150 if self.isDigitalVoice() or self.isPacket() or self.isPocsag() else self.squelch_level
if self.running:
self.pipes["squelch_pipe"].write("%g\n" % (self.convertToLinear(actual_squelch)))
def set_unvoiced_quality(self, q):
self.unvoiced_quality = q
self.restart()
def get_unvoiced_quality(self):
return self.unvoiced_quality
def set_dmr_filter(self, filter):
if self.has_pipe("dmr_control_pipe"):
self.pipes["dmr_control_pipe"].write("{0}\n".format(filter))
def ddc_transition_bw(self):
return self.ddc_transition_bw_rate * (self.if_samp_rate() / float(self.samp_rate))
def try_create_pipes(self, pipe_names, command_base):
for pipe_name, pipe_type in pipe_names.items():
if "{" + pipe_name + "}" in command_base:
p = self.pipe_base_path + pipe_name
encoding = None
# TODO make digiham output unicode and then change this here
# the whole pipe enoding feature onlye exists because of this
if pipe_name == "meta_pipe":
encoding = "cp437"
self.pipes[pipe_name] = Pipe.create(p, pipe_type, encoding=encoding)
else:
self.pipes[pipe_name] = None
def has_pipe(self, name):
return name in self.pipes and self.pipes[name] is not None
def try_delete_pipes(self, pipe_names):
for pipe_name in pipe_names:
if self.has_pipe(pipe_name):
self.pipes[pipe_name].close()
self.pipes[pipe_name] = None
def try_create_configs(self, command):
if "{direwolf_config}" in command:
self.direwolf_config = "{tmp_dir}/openwebrx_direwolf_{myid}.conf".format(
tmp_dir=self.temporary_directory, myid=id(self)
)
self.direwolf_port = KissClient.getFreePort()
file = open(self.direwolf_config, "w")
file.write(DirewolfConfig().getConfig(self.direwolf_port, self.is_service))
file.close()
else:
self.direwolf_config = None
self.direwolf_port = None
def try_delete_configs(self):
if self.direwolf_config:
try:
os.unlink(self.direwolf_config)
except FileNotFoundError:
# result suits our expectations. fine :)
pass
except Exception:
logger.exception("try_delete_configs()")
self.direwolf_config = None
def start(self):
with self.modification_lock:
if self.running:
return
self.running = True
command_base = " | ".join(self.chain(self.demodulator))
# create control pipes for csdr
self.try_create_pipes(self.pipe_names, command_base)
# send initial config through the pipes
if self.has_pipe("bpf_pipe"):
self.set_bpf(self.low_cut, self.high_cut)
if self.has_pipe("shift_pipe"):
self.set_offset_freq(self.offset_freq)
if self.has_pipe("squelch_pipe"):
self.set_squelch_level(self.squelch_level)
if self.has_pipe("dmr_control_pipe"):
self.set_dmr_filter(3)
# run the command
command = command_base.format(
bpf_pipe=self.pipes["bpf_pipe"],
shift_pipe=self.pipes["shift_pipe"],
squelch_pipe=self.pipes["squelch_pipe"],
smeter_pipe=self.pipes["smeter_pipe"],
meta_pipe=self.pipes["meta_pipe"],
iqtee_pipe=self.pipes["iqtee_pipe"],
iqtee2_pipe=self.pipes["iqtee2_pipe"],
dmr_control_pipe=self.pipes["dmr_control_pipe"],
decimation=self.decimation,
last_decimation=self.last_decimation,
fft_size=self.fft_size,
fft_block_size=self.fft_block_size(),
fft_averages=self.fft_averages,
bpf_transition_bw=float(self.bpf_transition_bw) / self.if_samp_rate(),
ddc_transition_bw=self.ddc_transition_bw(),
flowcontrol=int(self.samp_rate * 2),
start_bufsize=self.base_bufsize * self.decimation,
nc_port=self.nc_port,
output_rate=self.get_output_rate(),
smeter_report_every=int(self.if_samp_rate() / 6000),
unvoiced_quality=self.get_unvoiced_quality(),
audio_rate=self.get_audio_rate(),
)
logger.debug("Command = %s", command)
my_env = os.environ.copy()
if self.csdr_dynamic_bufsize:
my_env["CSDR_DYNAMIC_BUFSIZE_ON"] = "1"
if self.csdr_print_bufsizes:
my_env["CSDR_PRINT_BUFSIZES"] = "1"
out = subprocess.PIPE if self.output.supports_type("audio") else subprocess.DEVNULL
self.process = subprocess.Popen(command, stdout=out, shell=True, start_new_session=True, env=my_env)
def watch_thread():
rc = self.process.wait()
logger.debug("dsp thread ended with rc=%d", rc)
if rc == 0 and self.running and not self.modification_lock.locked():
logger.debug("restarting since rc = 0, self.running = true, and no modification")
self.restart()
threading.Thread(target=watch_thread).start()
if self.output.supports_type("audio"):
self.output.send_output(
"audio",
partial(
self.process.stdout.read,
self.get_fft_bytes_to_read() if self.demodulator == "fft" else self.get_audio_bytes_to_read(),
),
)
self.start_secondary_demodulator()
if self.has_pipe("smeter_pipe"):
def read_smeter():
raw = self.pipes["smeter_pipe"].readline()
if len(raw) == 0:
return None
else:
return float(raw.rstrip("\n"))
self.output.send_output("smeter", read_smeter)
if self.has_pipe("meta_pipe"):
def read_meta():
raw = self.pipes["meta_pipe"].readline()
if len(raw) == 0:
return None
else:
return raw.rstrip("\n")
self.output.send_output("meta", read_meta)
if self.csdr_dynamic_bufsize:
self.process.stdout.read(8) # dummy read to skip bufsize & preamble
logger.debug("Note: CSDR_DYNAMIC_BUFSIZE_ON = 1")
def stop(self):
with self.modification_lock:
self.running = False
if self.process is not None:
try:
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
self.process = None
except ProcessLookupError:
# been killed by something else, ignore
pass
self.stop_secondary_demodulator()
self.try_delete_pipes(self.pipe_names)
def restart(self):
if not self.running:
return
self.stop()
self.start()
def __del__(self):
self.stop()

135
debian/changelog vendored
View File

@ -1,135 +0,0 @@
openwebrx (0.19.1) buster focal; urgency=low
* Added ability to authenticate receivers with listing sites using
"receiver id" tokens
-- Jakob Ketterl <jakob.ketterl@gmx.de> Sat, 13 Jun 2020 16:46:00 +0000
openwebrx (0.19.0) buster focal; urgency=low
* Fix direwolf connection setup by implementing a retry loop
* Pass direct sampling mode changes for rtl_sdr_soapy to owrx_connector
* OSM maps instead of Google when google_maps_api_key is not set (thanks
@jquagga)
* Improved logic to pass parameters to soapy devices.
- `rtl_sdr_soapy`: added support for `bias_tee`
- `sdrplay`: added support for `bias_tee`, `rf_notch` and `dab_notch`
- `airspy`: added support for `bitpack`
* Added support for Perseus-SDR devices, (thanks @amontefusco)
* Property System has been rewritten so that defaults on sdr behave as
expected
* Waterfall range auto-adjustment now only takes the center 80% of the
spectrum into account, which should work better with SDRs that oversample
or have rather flat filter curves towards the spectrum edges
* Bugfix for negative network usage
* FiFi SDR: prevent arecord from shutting down after 2GB of data has been
sent
* Added support for bias tee control on rtl_sdr devices
* All connector driven SDRs now support `"rf_gain": "auto"` to enable AGC
* `rtl_sdr` type now also supports the `direct_sampling` option
* Added decoding implementation for for digimode "JS8Call" (requires an
installation of js8call and the js8py library)
* Reorganization of the frontend demodulator code
* Improve receiver load time by concatenating javascript assets
* HackRF support is now based on SoapyHackRF
* Removed sdr.hu server listing support since the site has been shut down
* Added support for Radioberry 2 Rasbperry Pi SDR Cape
-- Jakob Ketterl <jakob.ketterl@gmx.de> Mon, 01 Jun 2020 17:02:00 +0000
openwebrx (0.18.0) buster; urgency=low
* Compression, resampling and filtering in the frontend have been rewritten
in javascript, sdr.js has been removed
* Decoding of Pocsag modulation is now possible
* Removed the 3D waterfall since it had no real application and required ~1MB
of javascript code to be downloaded
* Improved the frontend handling of the "too many users" scenario
* PSK63 digimode is now available (same decoding pipeline as PSK31, but with
adopted parameters)
* The frequency can now be manipulated with the mousewheel, which should
allow the user to tune more precise. The tuning step size is determined by
the digit the mouse cursor is hovering over.
* Clicking on the frequency now opens an input for direct frequency selection
* URL hashes have been fixed and improved: They are now updated
automatically, so a shared URL will include frequency and demodulator,
which allows for improved sharing and linking.
* New daylight scheduler for background decoding, allows profiles to be
selected by local sunrise / sunset times
* The owrx_connector is now the default way of communicating with sdr
devices. The old sdr types have been replaced, all `_connector` suffixes on
the type must be removed!
* The sources have been refactored, making it a lot easier to add support for
other devices
* SDR device failure handling has been improved, including user feedback
* New devices supported:
* wsjt-x updated to 2.1.2
* The rtl_tcp compatibility mode of the owrx_connector is now configurable
using the `rtltcp_compat` flag
* explicit device filter for soapy devices for multi-device setups
* compatibility fixes for safari browsers (ios and mac)
* Offset tuning using the `lfo_offset` has been reworked in a way that
`center_freq` has to be set to the frequency you actually want to listen
to. If you're using an `lfo_offset` already, you will probably need to
change its sign.
* `initial_squelch_level` can now be set on each profile.
* Part of the frontend code has been reworked
- Audio buffer minimums have been completely stripped. As a result, you
should get better latency. Unfortunately, this also means there will be
some skipping when audio starts.
- Now also supports AudioWorklets (for those browser that have it).
- Mousewheel controls for the receiver sliders
* Error handling for failed SDR devices
* One of the most-requested features is finally coming to OpenWebRX:
Bookmarks (sometimes also referred to as labels).
There's two kinds of bookmarks available:
- Serverside bookmarks that are set up by the receiver administrator.
Check the file `bookmarks.json` for examples!
- Clientside bookmarks which every user can store for themselves. They are
stored in the browser's localStorage.
* Automatic reporting of spots to [pskreporter](https://pskreporter.info/) is
now possible. Please have a look at the configuration on how to set it up.
* Websocket communication has been overhauled in large parts. It should now
be more reliable, and failing connections should now have no impact on
other users.
* Profile scheduling allows to set up band-hopping if you are running
background services.
* APRS now has the ability to show symbols on the map, if a corresponding
symbol set has been installed. Check the config!
* Debug logging has been disabled in a handful of modules, expect vastly
reduced output on the shell.
* New set of APRS-related features
- Decode Packet transmissions using direwolf (1k2 only for now)
- APRS packets are mostly decoded and shown both in a new panel and on the
map
- APRS is also available as a background service
- direwolfs I-gate functionality can be enabled, which allows your receiver
to work as a receive-only I-gate for the APRS network in the background
* Demodulation for background services has been optimized to use less total
bandwidth, saving CPU
* More metrics have been added; they can be used together with collectd and
its curl_json plugin for now, with some limitations.
* New bandplan feature, the first thing visible is the "dial" indicator that
brings you right to the dial frequency for digital modes
* fixed some bugs in the websocket communication which broke the map
* WSJT-X integration (FT8, FT4, WSPR, JT65, JT9 using wsjt-x demodulators)
* New Map Feature that shows both decoded grid squares from FT8 and Locations
decoded from YSF digital voice
* New Feature report that will show what functionality is available
* major rework on the openwebrx core
* Support of multiple SDR devices simultaneously
* Support for multiple profiles per SDR that allow the user to listen to
different frequencies
* Support for digital voice decoding
* Feature detection that will disable functionality when dependencies are not
available (if you're missing the digital
buttons, this is probably why)
* Support added for the following SDR sources:
- LimeSDR (`"type": "lime_sdr"`)
- PlutoSDR (`"type": "pluto_sdr"`)
- RTL_SDR via Soapy (`"type": "rtl_sdr_soapy"`) on special request to allow
use of the direct sampling mode
- SoapyRemote (`"type": "soapy_remote"`)
- FiFiSDR (`"type": "fifi_sdr"`)
- airspyhf devices (Airspy HF+ / Discovery) (`"type": "airspyhf"`)
-- Jakob Ketterl <jakob.ketterl@gmx.de> Tue, 18 Feb 2020 20:09:00 +0000

1
debian/compat vendored
View File

@ -1 +0,0 @@
10

16
debian/control vendored
View File

@ -1,16 +0,0 @@
Source: openwebrx
Maintainer: Jakob Ketterl <jakob.ketterl@gmx.de>
Section: hamradio
Priority: optional
Standards-Version: 4.2.0
Build-Depends: debhelper (>= 11), dh-python, python3-all (>= 3.5), python3-setuptools
Homepage: https://www.openwebrx.de/
Vcs-Browser: https://github.com/jketterl/openwebrx
Vcs-Git: https://github.com/jketterl/openwebrx.git
Package: openwebrx
Architecture: all
Depends: adduser, python3 (>= 3.5), python3-pkg-resources, csdr (>= 0.14), netcat, owrx-connector (>= 0.2), python3-js8py (>= 0.1), ${python3:Depends}, ${misc:Depends}
Recommends: digiham (>= 0.3), dsd (>= 1.7), sox, direwolf (>= 1.4), wsjtx, soapysdr-tools
Description: multi-user web sdr
Open source, multi-user SDR receiver with a web interface

View File

@ -1,5 +0,0 @@
config_webrx.py etc/openwebrx/
bands.json etc/openwebrx/
bookmarks.json etc/openwebrx/
users.json etc/openwebrx/
systemd/openwebrx.service lib/systemd/system/

7
debian/postinst vendored
View File

@ -1,7 +0,0 @@
#!/bin/bash
set -euxo pipefail
adduser --system --group --no-create-home --home /nonexistant openwebrx
usermod -aG plugdev openwebrx
#DEBHELPER#

5
debian/rules vendored
View File

@ -1,5 +0,0 @@
#!/usr/bin/make -f
export PYBUILD_NAME=openwebrx
%:
dh $@ --with python3 --buildsystem=pybuild --with systemd

View File

@ -1 +0,0 @@
3.0 (native)

View File

@ -1,8 +0,0 @@
ARG ARCHTAG
FROM openwebrx-soapysdr-base:$ARCHTAG
ADD docker/scripts/install-dependencies-airspy.sh /
RUN /install-dependencies-airspy.sh &&\
rm /install-dependencies-airspy.sh
ADD . /opt/openwebrx

View File

@ -1,18 +0,0 @@
FROM debian:buster-slim
ADD docker/files/js8call/js8call-hamlib.patch /
ADD docker/files/wsjtx/*.patch /
ADD docker/scripts/install-dependencies.sh /
RUN /install-dependencies.sh && \
rm /install-dependencies.sh && \
rm /*.patch
ENTRYPOINT ["/init"]
WORKDIR /opt/openwebrx
VOLUME /etc/openwebrx
CMD [ "/opt/openwebrx/docker/scripts/run.sh" ]
EXPOSE 8073

View File

@ -1,26 +0,0 @@
ARG ARCHTAG
FROM openwebrx-base:$ARCHTAG
ADD docker/scripts/install-dependencies-*.sh /
ADD docker/files/sdrplay/install-lib.*.patch /
RUN /install-dependencies-rtlsdr.sh &&\
/install-dependencies-soapysdr.sh &&\
/install-dependencies-hackrf.sh &&\
/install-dependencies-sdrplay.sh &&\
/install-dependencies-airspy.sh &&\
/install-dependencies-rtlsdr-soapy.sh &&\
/install-dependencies-plutosdr.sh &&\
/install-dependencies-limesdr.sh &&\
/install-dependencies-soapyremote.sh &&\
/install-dependencies-perseus.sh &&\
rm /install-dependencies-*.sh &&\
rm /install-lib.*.patch
ADD docker/scripts/install-connectors.sh /
RUN /install-connectors.sh &&\
rm /install-connectors.sh
ADD docker/files/services/sdrplay /etc/services.d/sdrplay
ADD . /opt/openwebrx

View File

@ -1,8 +0,0 @@
ARG ARCHTAG
FROM openwebrx-soapysdr-base:$ARCHTAG
ADD docker/scripts/install-dependencies-hackrf.sh /
RUN /install-dependencies-hackrf.sh &&\
rm /install-dependencies-hackrf.sh
ADD . /opt/openwebrx

View File

@ -1,8 +0,0 @@
ARG ARCHTAG
FROM openwebrx-soapysdr-base:$ARCHTAG
ADD docker/scripts/install-dependencies-limesdr.sh /
RUN /install-dependencies-limesdr.sh &&\
rm /install-dependencies-limesdr.sh
ADD . /opt/openwebrx

View File

@ -1,8 +0,0 @@
ARG ARCHTAG
FROM openwebrx-base:$ARCHTAG
ADD docker/scripts/install-dependencies-perseus.sh /
RUN /install-dependencies-perseus.sh &&\
rm /install-dependencies-perseus.sh
ADD . /opt/openwebrx

View File

@ -1,8 +0,0 @@
ARG ARCHTAG
FROM openwebrx-soapysdr-base:$ARCHTAG
ADD docker/scripts/install-dependencies-plutosdr.sh /
RUN /install-dependencies-plutosdr.sh &&\
rm /install-dependencies-plutosdr.sh
ADD . /opt/openwebrx

View File

@ -1,12 +0,0 @@
ARG ARCHTAG
FROM openwebrx-base:$ARCHTAG
ADD docker/scripts/install-dependencies-rtlsdr.sh /
ADD docker/scripts/install-connectors.sh /
RUN /install-dependencies-rtlsdr.sh &&\
rm /install-dependencies-rtlsdr.sh &&\
/install-connectors.sh &&\
rm /install-connectors.sh
ADD . /opt/openwebrx

View File

@ -1,8 +0,0 @@
ARG ARCHTAG
FROM openwebrx-soapysdr-base:$ARCHTAG
ADD docker/scripts/install-dependencies-rtlsdr-soapy.sh /
RUN /install-dependencies-rtlsdr-soapy.sh &&\
rm /install-dependencies-rtlsdr-soapy.sh
ADD . /opt/openwebrx

View File

@ -1,12 +0,0 @@
ARG ARCHTAG
FROM openwebrx-soapysdr-base:$ARCHTAG
ADD docker/scripts/install-dependencies-sdrplay.sh /
ADD docker/files/sdrplay/install-lib.*.patch /
RUN /install-dependencies-sdrplay.sh &&\
rm /install-dependencies-sdrplay.sh &&\
rm /install-lib.*.patch
ADD docker/files/services/sdrplay /etc/services.d/sdrplay
ADD . /opt/openwebrx

View File

@ -1,8 +0,0 @@
ARG ARCHTAG
FROM openwebrx-soapysdr-base:$ARCHTAG
ADD docker/scripts/install-dependencies-soapyremote.sh /
RUN /install-dependencies-soapyremote.sh &&\
rm /install-dependencies-soapyremote.sh
ADD . /opt/openwebrx

View File

@ -1,9 +0,0 @@
ARG ARCHTAG
FROM openwebrx-base:$ARCHTAG
ADD docker/scripts/install-dependencies-soapysdr.sh /
ADD docker/scripts/install-connectors.sh /
RUN /install-dependencies-soapysdr.sh &&\
rm /install-dependencies-soapysdr.sh &&\
/install-connectors.sh &&\
rm /install-connectors.sh

View File

@ -1,5 +0,0 @@
ARCH=$(uname -m)
IMAGES="openwebrx-rtlsdr openwebrx-sdrplay openwebrx-hackrf openwebrx-airspy openwebrx-rtlsdr-soapy openwebrx-plutosdr openwebrx-limesdr openwebrx-soapyremote openwebrx-perseus openwebrx-full openwebrx"
ALL_ARCHS="x86_64 armv7l aarch64"
TAG=${TAG:-"latest"}
ARCHTAG="$TAG-$ARCH"

View File

@ -1,150 +0,0 @@
diff -ur js8call-orig/CMake/Modules/Findhamlib.cmake js8call/CMake/Modules/Findhamlib.cmake
--- js8call-orig/CMake/Modules/Findhamlib.cmake 2020-05-28 00:10:13.386429366 +0200
+++ js8call/CMake/Modules/Findhamlib.cmake 2020-05-28 00:10:34.339623106 +0200
@@ -78,4 +78,4 @@
# Handle the QUIETLY and REQUIRED arguments and set HAMLIB_FOUND to
# TRUE if all listed variables are TRUE
include (FindPackageHandleStandardArgs)
-find_package_handle_standard_args (hamlib DEFAULT_MSG hamlib_INCLUDE_DIRS hamlib_LIBRARIES hamlib_LIBRARY_DIRS)
+find_package_handle_standard_args (hamlib DEFAULT_MSG hamlib_INCLUDE_DIRS hamlib_LIBRARIES)
diff -ur js8call-orig/CMakeLists.txt js8call/CMakeLists.txt
--- js8call-orig/CMakeLists.txt 2020-05-28 00:10:13.393095987 +0200
+++ js8call/CMakeLists.txt 2020-05-28 00:12:09.925653037 +0200
@@ -683,7 +683,7 @@
#
# libhamlib setup
#
-set (hamlib_STATIC 1)
+set (hamlib_STATIC 0)
find_package (hamlib 3 REQUIRED)
find_program (RIGCTL_EXE rigctl)
find_program (RIGCTLD_EXE rigctld)
@@ -1033,55 +1033,6 @@
target_link_libraries (js8 wsjt_fort wsjt_cxx Qt5::Core)
endif (${OPENMP_FOUND} OR APPLE)
-# build the main application
-add_executable (js8call MACOSX_BUNDLE
- ${sqlite3_CSRCS}
- ${wsjtx_CXXSRCS}
- ${wsjtx_GENUISRCS}
- wsjtx.rc
- ${WSJTX_ICON_FILE}
- ${wsjtx_RESOURCES_RCC}
- )
-
-if (WSJT_CREATE_WINMAIN)
- set_target_properties (js8call PROPERTIES WIN32_EXECUTABLE ON)
-endif (WSJT_CREATE_WINMAIN)
-
-set_target_properties (js8call PROPERTIES
- MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Darwin/Info.plist.in"
- MACOSX_BUNDLE_INFO_STRING "${WSJTX_DESCRIPTION_SUMMARY}"
- MACOSX_BUNDLE_ICON_FILE "${WSJTX_ICON_FILE}"
- MACOSX_BUNDLE_BUNDLE_VERSION ${wsjtx_VERSION}
- MACOSX_BUNDLE_SHORT_VERSION_STRING "v${wsjtx_VERSION}"
- MACOSX_BUNDLE_LONG_VERSION_STRING "Version ${wsjtx_VERSION}"
- MACOSX_BUNDLE_BUNDLE_NAME "${PROJECT_NAME}"
- MACOSX_BUNDLE_BUNDLE_EXECUTABLE_NAME "${PROJECT_NAME}"
- MACOSX_BUNDLE_COPYRIGHT "${PROJECT_COPYRIGHT}"
- MACOSX_BUNDLE_GUI_IDENTIFIER "org.kn4crd.js8call"
- )
-
-target_include_directories (js8call PRIVATE ${FFTW3_INCLUDE_DIRS})
-if (APPLE)
- target_link_libraries (js8call wsjt_fort wsjt_cxx wsjt_qt wsjt_qtmm ${hamlib_LIBRARIES} ${FFTW3_LIBRARIES})
-else ()
- target_link_libraries (js8call wsjt_fort_omp wsjt_cxx wsjt_qt wsjt_qtmm ${hamlib_LIBRARIES} ${FFTW3_LIBRARIES})
- if (OpenMP_C_FLAGS)
- set_target_properties (js8call PROPERTIES
- COMPILE_FLAGS "${OpenMP_C_FLAGS}"
- LINK_FLAGS "${OpenMP_C_FLAGS}"
- )
- endif ()
- set_target_properties (js8call PROPERTIES
- Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/fortran_modules_omp
- )
- if (WIN32)
- set_target_properties (js8call PROPERTIES
- LINK_FLAGS -Wl,--stack,16777216
- )
- endif ()
-endif ()
-qt5_use_modules (js8call SerialPort) # not sure why the interface link library syntax above doesn't work
-
# if (UNIX)
# if (NOT WSJT_SKIP_MANPAGES)
# add_subdirectory (manpages)
@@ -1097,38 +1048,10 @@
#
# installation
#
-install (TARGETS js8call
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
- BUNDLE DESTINATION . COMPONENT runtime
- )
-
install (TARGETS js8 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
)
-install (PROGRAMS
- ${RIGCTL_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctl-local${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (PROGRAMS
- ${RIGCTLD_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctld-local${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (FILES
- README
- COPYING
- INSTALL
- INSTALL-WSJTX
- DESTINATION ${CMAKE_INSTALL_DOCDIR}
- #COMPONENT runtime
- )
-
install (FILES
contrib/Ephemeris/JPLEPH
DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}
@@ -1182,32 +1105,6 @@
"${CMAKE_CURRENT_BINARY_DIR}/wsjtx_config.h"
)
-
-if (NOT WIN32 AND NOT APPLE)
- # install a desktop file so js8call appears in the application start
- # menu with an icon
- install (
- FILES js8call.desktop
- DESTINATION /usr/share/applications
- #COMPONENT runtime
- )
- install (
- FILES icons/Unix/js8call_icon.png
- DESTINATION /usr/share/pixmaps
- #COMPONENT runtime
- )
-
- IF("${CMAKE_INSTALL_PREFIX}" STREQUAL "/opt/js8call")
- execute_process(COMMAND ln -s /opt/js8call/bin/js8call ljs8call)
-
- install(FILES
- ${CMAKE_BINARY_DIR}/ljs8call DESTINATION /usr/bin/ RENAME js8call
- #COMPONENT runtime
- )
- endif()
-endif (NOT WIN32 AND NOT APPLE)
-
-
#
# bundle fixup only done in Release or MinSizeRel configurations
#
Only in js8call/: .idea

View File

@ -1,23 +0,0 @@
diff -ur sdrplay-orig/install_lib.sh sdrplay/install_lib.sh
--- sdrplay-orig/install_lib.sh 2020-05-24 14:30:06.022483867 +0000
+++ sdrplay/install_lib.sh 2020-05-24 14:30:49.093435726 +0000
@@ -4,19 +4,6 @@
export MAJVERS="3"
echo "Installing SDRplay RSP API library ${VERS}..."
-read -p "Press RETURN to view the license agreement" ret
-
-more sdrplay_license.txt
-
-while true; do
- echo "Press y and RETURN to accept the license agreement and continue with"
- read -p "the installation, or press n and RETURN to exit the installer [y/n] " yn
- case $yn in
- [Yy]* ) break;;
- [Nn]* ) exit;;
- * ) echo "Please answer y or n";;
- esac
-done
export ARCH=`uname -m`

View File

@ -1,40 +0,0 @@
diff -ur sdrplay-orig/install_lib.sh sdrplay/install_lib.sh
--- sdrplay-orig/install_lib.sh 2020-05-24 14:13:04.561271707 +0000
+++ sdrplay/install_lib.sh 2020-05-24 14:16:20.068329040 +0000
@@ -4,19 +4,6 @@
MAJVERS="3"
echo "Installing SDRplay RSP API library ${VERS}..."
-read -p "Press RETURN to view the license agreement" ret
-
-more sdrplay_license.txt
-
-while true; do
- echo "Press y and RETURN to accept the license agreement and continue with"
- read -p "the installation, or press n and RETURN to exit the installer [y/n] " yn
- case $yn in
- [Yy]* ) break;;
- [Nn]* ) exit;;
- * ) echo "Please answer y or n";;
- esac
-done
ARCH=`uname -m`
@@ -141,16 +128,6 @@
echo "SDRplay API ${VERS} Installation Finished"
echo " "
-while true; do
- echo "Would you like to add SDRplay USB IDs to the local database for easier
-"
- read -p "identification in applications such as lsusb? [y/n] " yn
- case $yn in
- [Yy]* ) break;;
- [Nn]* ) exit;;
- * ) echo "Please answer y or n";;
- esac
-done
sudo cp scripts/sdrplay_usbids.sh ${INSTALLBINDIR}/.
sudo chmod 755 ${INSTALLBINDIR}/sdrplay_usbids.sh
sudo cp scripts/sdrplay_ids.txt ${INSTALLBINDIR}/.

View File

@ -1,39 +0,0 @@
diff -ur sdrplay-orig/install_lib.sh sdrplay/install_lib.sh
--- sdrplay-orig/install_lib.sh 2020-05-24 13:56:56.622000041 +0000
+++ sdrplay/install_lib.sh 2020-05-24 13:58:51.837801559 +0000
@@ -4,19 +4,6 @@
MAJVERS="3"
echo "Installing SDRplay RSP API library ${VERS}..."
-read -p "Press RETURN to view the license agreement" ret
-
-more sdrplay_license.txt
-
-while true; do
- echo "Press y and RETURN to accept the license agreement and continue with"
- read -p "the installation, or press n and RETURN to exit the installer [y/n] " yn
- case $yn in
- [Yy]* ) break;;
- [Nn]* ) exit;;
- * ) echo "Please answer y or n";;
- esac
-done
ARCH=`uname -m`
OSDIST="Unknown"
@@ -157,15 +144,6 @@
echo " "
echo "SDRplay API ${VERS} Installation Finished"
echo " "
-while true; do
- echo "Would you like to add SDRplay USB IDs to the local database for easier"
- read -p "identification in applications such as lsusb? [y/n] " yn
- case $yn in
- [Yy]* ) break;;
- [Nn]* ) exit;;
- * ) echo "Please answer y or n";;
- esac
-done
sudo cp scripts/sdrplay_usbids.sh ${INSTALLBINDIR}/.
sudo chmod 755 ${INSTALLBINDIR}/sdrplay_usbids.sh
sudo cp scripts/sdrplay_ids.txt ${INSTALLBINDIR}/.

View File

@ -1,2 +0,0 @@
#!/usr/bin/execlineb -P
/usr/local/bin/sdrplay_apiService

View File

@ -1,43 +0,0 @@
--- CMakeLists.txt 2020-05-25 19:26:41.423517236 +0200
+++ CMakeLists.txt 2020-05-25 19:11:36.116236231 +0200
@@ -79,24 +79,6 @@
include (ExternalProject)
-
-#
-# build and install hamlib locally so it can be referenced by the
-# WSJT-X build
-#
-ExternalProject_Add (hamlib
- GIT_REPOSITORY ${hamlib_repo}
- GIT_TAG ${hamlib_TAG}
- URL ${CMAKE_CURRENT_SOURCE_DIR}/src/hamlib.tgz
- URL_HASH MD5=${hamlib_md5sum}
- UPDATE_COMMAND ./bootstrap
- PATCH_COMMAND ${PATCH_EXECUTABLE} -p1 -N < ${CMAKE_CURRENT_SOURCE_DIR}/hamlib.patch
- CONFIGURE_COMMAND <SOURCE_DIR>/configure --prefix=<INSTALL_DIR> --disable-shared --enable-static --without-cxx-binding ${EXTRA_FLAGS} # LIBUSB_LIBS=${USB_LIBRARY}
- BUILD_COMMAND $(MAKE) all V=1 # $(MAKE) is ExternalProject_Add() magic to do recursive make
- INSTALL_COMMAND $(MAKE) install-strip V=1 DESTDIR=""
- STEP_TARGETS update install
- )
-
#
# custom target to make a hamlib source tarball
#
@@ -128,7 +110,6 @@
# build and optionally install WSJT-X using the hamlib package built
# above
#
-ExternalProject_Get_Property (hamlib INSTALL_DIR)
ExternalProject_Add (wsjtx
GIT_REPOSITORY ${wsjtx_repo}
GIT_TAG ${WSJTX_TAG}
@@ -152,7 +133,6 @@
DEPENDEES build
)
-set_target_properties (hamlib PROPERTIES EXCLUDE_FROM_ALL 1)
set_target_properties (wsjtx PROPERTIES EXCLUDE_FROM_ALL 1)
add_dependencies (wsjtx-configure hamlib-install)

View File

@ -1,156 +0,0 @@
diff -ur wsjtx-orig/CMake/Modules/Findhamlib.cmake wsjtx/CMake/Modules/Findhamlib.cmake
--- wsjtx-orig/CMake/Modules/Findhamlib.cmake 2020-05-27 22:41:57.774855748 +0200
+++ wsjtx/CMake/Modules/Findhamlib.cmake 2020-05-27 22:42:35.267939882 +0200
@@ -85,4 +85,4 @@
# Handle the QUIETLY and REQUIRED arguments and set HAMLIB_FOUND to
# TRUE if all listed variables are TRUE
include (FindPackageHandleStandardArgs)
-find_package_handle_standard_args (hamlib DEFAULT_MSG hamlib_INCLUDE_DIRS hamlib_LIBRARIES hamlib_LIBRARY_DIRS)
+find_package_handle_standard_args (hamlib DEFAULT_MSG hamlib_INCLUDE_DIRS hamlib_LIBRARIES)
diff -ur wsjtx-orig/CMakeLists.txt wsjtx/CMakeLists.txt
--- wsjtx-orig/CMakeLists.txt 2020-05-27 22:41:57.774855748 +0200
+++ wsjtx/CMakeLists.txt 2020-05-27 22:58:18.905001618 +0200
@@ -869,7 +869,7 @@
#
# libhamlib setup
#
-set (hamlib_STATIC 1)
+set (hamlib_STATIC 0)
find_package (hamlib 3 REQUIRED)
find_program (RIGCTL_EXE rigctl)
find_program (RIGCTLD_EXE rigctld)
@@ -1326,54 +1326,10 @@
endif(WSJT_BUILD_UTILS)
-# build the main application
-add_executable (wsjtx MACOSX_BUNDLE
- ${wsjtx_CXXSRCS}
- ${wsjtx_GENUISRCS}
- wsjtx.rc
- ${WSJTX_ICON_FILE}
- ${wsjtx_RESOURCES_RCC}
- )
-
if (WSJT_CREATE_WINMAIN)
set_target_properties (wsjtx PROPERTIES WIN32_EXECUTABLE ON)
endif (WSJT_CREATE_WINMAIN)
-set_target_properties (wsjtx PROPERTIES
- MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Darwin/Info.plist.in"
- MACOSX_BUNDLE_INFO_STRING "${WSJTX_DESCRIPTION_SUMMARY}"
- MACOSX_BUNDLE_ICON_FILE "${WSJTX_ICON_FILE}"
- MACOSX_BUNDLE_BUNDLE_VERSION ${wsjtx_VERSION}
- MACOSX_BUNDLE_SHORT_VERSION_STRING "v${wsjtx_VERSION}"
- MACOSX_BUNDLE_LONG_VERSION_STRING "Version ${wsjtx_VERSION}"
- MACOSX_BUNDLE_BUNDLE_NAME "${PROJECT_NAME}"
- MACOSX_BUNDLE_BUNDLE_EXECUTABLE_NAME "${PROJECT_NAME}"
- MACOSX_BUNDLE_COPYRIGHT "${PROJECT_COPYRIGHT}"
- MACOSX_BUNDLE_GUI_IDENTIFIER "org.k1jt.wsjtx"
- )
-
-target_include_directories (wsjtx PRIVATE ${FFTW3_INCLUDE_DIRS})
-if (APPLE)
- target_link_libraries (wsjtx wsjt_fort wsjt_cxx wsjt_qt wsjt_qtmm ${hamlib_LIBRARIES} ${FFTW3_LIBRARIES})
-else ()
- target_link_libraries (wsjtx wsjt_fort_omp wsjt_cxx wsjt_qt wsjt_qtmm ${hamlib_LIBRARIES} ${FFTW3_LIBRARIES})
- if (OpenMP_C_FLAGS)
- set_target_properties (wsjtx PROPERTIES
- COMPILE_FLAGS "${OpenMP_C_FLAGS}"
- LINK_FLAGS "${OpenMP_C_FLAGS}"
- )
- endif ()
- set_target_properties (wsjtx PROPERTIES
- Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/fortran_modules_omp
- )
- if (WIN32)
- set_target_properties (wsjtx PROPERTIES
- LINK_FLAGS -Wl,--stack,16777216
- )
- endif ()
-endif ()
-qt5_use_modules (wsjtx SerialPort) # not sure why the interface link library syntax above doesn't work
-
# make a library for WSJT-X UDP servers
# add_library (wsjtx_udp SHARED ${UDP_library_CXXSRCS})
add_library (wsjtx_udp-static STATIC ${UDP_library_CXXSRCS})
@@ -1417,24 +1373,9 @@
set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)
endif (WSJT_CREATE_WINMAIN)
-if (UNIX)
- if (NOT WSJT_SKIP_MANPAGES)
- add_subdirectory (manpages)
- add_dependencies (wsjtx manpages)
- endif (NOT WSJT_SKIP_MANPAGES)
- if (NOT APPLE)
- add_subdirectory (debian)
- add_dependencies (wsjtx debian)
- endif (NOT APPLE)
-endif (UNIX)
-
#
# installation
#
-install (TARGETS wsjtx
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
- BUNDLE DESTINATION . COMPONENT runtime
- )
# install (TARGETS wsjtx_udp EXPORT udp
# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
@@ -1453,12 +1394,7 @@
# DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/wsjtx
# )
-install (TARGETS udp_daemon message_aggregator
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
- BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
- )
-
-install (TARGETS jt9 wsprd fmtave fcal fmeasure
+install (TARGETS jt9 wsprd
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
)
@@ -1471,39 +1407,6 @@
)
endif(WSJT_BUILD_UTILS)
-install (PROGRAMS
- ${RIGCTL_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctl-wsjtx${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (PROGRAMS
- ${RIGCTLD_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctld-wsjtx${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (PROGRAMS
- ${RIGCTLCOM_EXE}
- DESTINATION ${CMAKE_INSTALL_BINDIR}
- #COMPONENT runtime
- RENAME rigctlcom-wsjtx${CMAKE_EXECUTABLE_SUFFIX}
- )
-
-install (FILES
- README
- COPYING
- AUTHORS
- THANKS
- NEWS
- INSTALL
- BUGS
- DESTINATION ${CMAKE_INSTALL_DOCDIR}
- #COMPONENT runtime
- )
-
install (FILES
contrib/Ephemeris/JPLEPH
DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}
Only in wsjtx: .idea

View File

@ -1,31 +0,0 @@
#!/usr/bin/env bash
set -euxo pipefail
export MAKEFLAGS="-j4"
function cmakebuild() {
cd $1
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
mkdir build
cd build
cmake ..
make
make install
cd ../..
rm -rf $1
}
cd /tmp
BUILD_PACKAGES="git cmake make gcc g++"
apt-get update
apt-get -y install --no-install-recommends $BUILD_PACKAGES
git clone https://github.com/jketterl/owrx_connector.git
cmakebuild owrx_connector 45ec227b38bb763b0a923a1856740f4ddf74216c
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,41 +0,0 @@
#!/bin/bash
set -euxo pipefail
export MAKEFLAGS="-j4"
function cmakebuild() {
cd $1
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
mkdir build
cd build
cmake ..
make
make install
cd ../..
rm -rf $1
}
cd /tmp
STATIC_PACKAGES="libusb-1.0-0"
BUILD_PACKAGES="git libusb-1.0-0-dev cmake make gcc g++ pkg-config"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/airspy/airspyone_host.git
cmakebuild airspyone_host bceca18f9e3a5f89cff78c4d949c71771d92dfd3
git clone https://github.com/pothosware/SoapyAirspy.git
cmakebuild SoapyAirspy 10d697b209e7f1acc8b2c8d24851d46170ef77e3
git clone https://github.com/airspy/airspyhf.git
cmakebuild airspyhf 613852a2bb64af42690bf9be2201826af69a9475
git clone https://github.com/pothosware/SoapyAirspyHF.git
cmakebuild SoapyAirspyHF 81ca737bb044dd930a9de738bced1e4915491f1b
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,39 +0,0 @@
#!/bin/bash
set -euxo pipefail
export MAKEFLAGS="-j4"
function cmakebuild() {
cd $1
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
mkdir build
cd build
cmake ..
make
make install
cd ../..
rm -rf $1
}
cd /tmp
STATIC_PACKAGES="libusb-1.0-0 libfftw3-3 udev"
BUILD_PACKAGES="git cmake make patch wget sudo gcc g++ libusb-1.0-0-dev libfftw3-dev pkg-config"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/mossmann/hackrf.git
cd hackrf
git checkout 43e6f99fe8543094d18ff3a6550ed2066c398862
cmakebuild host
cd ..
rm -rf hackrf
git clone https://github.com/pothosware/SoapyHackRF.git
cmakebuild SoapyHackRF 3c514cecd3dd2fdf4794aebc96c482940c11d7ff
SUDO_FORCE_REMOVE=yes apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,31 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
export MAKEFLAGS="-j4"
cd /tmp
STATIC_PACKAGES="libusb-1.0-0 libatomic1"
BUILD_PACKAGES="git libusb-1.0-0-dev cmake make gcc g++"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
SIMD_FLAGS=""
if [[ 'x86_64' == `uname -m` ]] ; then
SIMD_FLAGS="-DDEFAULT_SIMD_FLAGS=SSE3"
fi
git clone https://github.com/myriadrf/LimeSuite.git
cd LimeSuite
git checkout 0854a51ec06b30b01f19a562149c39461e92f24d
mkdir builddir
cd builddir
cmake .. -DENABLE_EXAMPLES=OFF -DENABLE_DESKTOP=OFF -DENABLE_LIME_UTIL=OFF -DENABLE_QUICKTEST=OFF -DENABLE_OCTAVE=OFF -DENABLE_GUI=OFF -DCMAKE_CXX_STANDARD_LIBRARIES="-latomic" ${SIMD_FLAGS}
make
make install
cd ../..
rm -rf LimeSuite
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -euxo pipefail
export MAKEFLAGS="-j4"
cd /tmp
STATIC_PACKAGES="libusb-1.0-0 libudev1"
BUILD_PACKAGES="git make gcc autoconf automake libtool libusb-1.0-0-dev xxd"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/Microtelecom/libperseus-sdr.git
cd libperseus-sdr
git checkout 72ac67c5b7936a1991be0ec97c03a59c1a8ac8f3
./bootstrap.sh
./configure
make
make install
ldconfig /etc/ld.so.conf.d
cd ..
rm -rf libperseus-sdr
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,38 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
export MAKEFLAGS="-j4"
function cmakebuild() {
cd $1
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
mkdir build
cd build
cmake .. ${3:-}
make
make install
cd ../..
rm -rf $1
}
cd /tmp
STATIC_PACKAGES="libusb-1.0-0 libxml2"
BUILD_PACKAGES="git libusb-1.0-0-dev cmake make gcc g++ libxml2-dev flex bison"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/analogdevicesinc/libiio.git
cmakebuild libiio 5f5af2e417129ad8f4e05fc5c1b730f0694dca12 -DCMAKE_INSTALL_PREFIX=/usr/local
git clone https://github.com/analogdevicesinc/libad9361-iio.git
cmakebuild libad9361-iio 8ac95f3325c18c2e34cd9cfd49c7b63d69a0a9d2
git clone https://github.com/pothosware/SoapyPlutoSDR.git
cmakebuild SoapyPlutoSDR c88b7f5bac1e5785f212f9a7c6ce8fef64eb719e
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,35 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
export MAKEFLAGS="-j4"
function cmakebuild() {
cd $1
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
mkdir build
cd build
cmake ..
make
make install
cd ../..
rm -rf $1
}
cd /tmp
STATIC_PACKAGES="libusb-1.0-0"
BUILD_PACKAGES="git libusb-1.0-0-dev cmake make gcc g++ pkg-config"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/osmocom/rtl-sdr.git
cmakebuild rtl-sdr d794155ba65796a76cd0a436f9709f4601509320
git clone https://github.com/pothosware/SoapyRTLSDR.git
cmakebuild SoapyRTLSDR 8ba18f17d64005e43ff2a4e46611f8c710b05007
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,32 +0,0 @@
#!/bin/bash
set -euxo pipefail
export MAKEFLAGS="-j4"
function cmakebuild() {
cd $1
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
mkdir build
cd build
cmake ..
make
make install
cd ../..
rm -rf $1
}
cd /tmp
STATIC_PACKAGES="libusb-1.0.0"
BUILD_PACKAGES="git libusb-1.0.0-dev cmake make gcc g++ pkg-config"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/osmocom/rtl-sdr.git
cmakebuild rtl-sdr d794155ba65796a76cd0a436f9709f4601509320
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,56 +0,0 @@
#!/bin/bash
set -euxo pipefail
export MAKEFLAGS="-j4"
function cmakebuild() {
cd $1
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
mkdir build
cd build
cmake ..
make
make install
cd ../..
rm -rf $1
}
cd /tmp
STATIC_PACKAGES="libusb-1.0.0 udev"
BUILD_PACKAGES="git cmake make patch wget sudo gcc g++ libusb-1.0-0-dev"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
ARCH=$(uname -m)
case $ARCH in
x86_64)
BINARY=SDRplay_RSP_API-Linux-3.07.1.run
;;
armv*)
BINARY=SDRplay_RSP_API-ARM32-3.07.2.run
;;
aarch64)
BINARY=SDRplay_RSP_API-ARM64-3.07.1.run
;;
esac
wget https://www.sdrplay.com/software/$BINARY
sh $BINARY --noexec --target sdrplay
patch --verbose -Np0 < /install-lib.$ARCH.patch
cd sdrplay
./install_lib.sh
cd ..
rm -rf sdrplay
rm $BINARY
git clone https://github.com/SDRplay/SoapySDRPlay.git
cmakebuild SoapySDRPlay 1c2728a04db5edf8154d02f5cca87e655152d7c1
SUDO_FORCE_REMOVE=yes apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,32 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
export MAKEFLAGS="-j4"
function cmakebuild() {
cd $1
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
mkdir build
cd build
cmake ..
make
make install
cd ../..
rm -rf $1
}
cd /tmp
STATIC_PACKAGES="avahi-daemon libavahi-client3"
BUILD_PACKAGES="git cmake make gcc g++ libavahi-client-dev"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/pothosware/SoapyRemote.git
cmakebuild SoapyRemote 6d9bd820da470cfe7b27b2e6946af93cfece448f
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,32 +0,0 @@
#!/bin/bash
set -euxo pipefail
export MAKEFLAGS="-j4"
function cmakebuild() {
cd $1
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
mkdir build
cd build
cmake ..
make
make install
cd ../..
rm -rf $1
}
cd /tmp
STATIC_PACKAGES="libudev1"
BUILD_PACKAGES="git cmake make patch wget sudo gcc g++"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/pothosware/SoapySDR
cmakebuild SoapySDR f722f9ce5b629c3c44401a9bf628b3f8e67a9695
SUDO_FORCE_REMOVE=yes apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,109 +0,0 @@
#!/bin/bash
set -euxo pipefail
export MAKEFLAGS="-j4"
function cmakebuild() {
cd $1
if [[ ! -z "${2:-}" ]]; then
git checkout $2
fi
mkdir build
cd build
cmake ${CMAKE_ARGS:-} ..
make
make install
cd ../..
rm -rf $1
}
cd /tmp
STATIC_PACKAGES="sox libfftw3-bin python3 python3-setuptools netcat-openbsd libsndfile1 liblapack3 libusb-1.0-0 libqt5core5a libreadline7 libgfortran4 libgomp1 libasound2 libudev1 ca-certificates"
BUILD_PACKAGES="wget git libsndfile1-dev libfftw3-dev cmake make gcc g++ liblapack-dev autoconf automake libtool texinfo gfortran libusb-1.0-0-dev qtbase5-dev qtmultimedia5-dev qttools5-dev libqt5serialport5-dev qttools5-dev-tools asciidoctor asciidoc libasound2-dev pkg-config libudev-dev libhamlib-dev patch xsltproc"
apt-get update
apt-get -y install auto-apt-proxy
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
case `uname -m` in
arm*)
PLATFORM=armhf
;;
aarch64*)
PLATFORM=aarch64
;;
x86_64*)
PLATFORM=amd64
;;
esac
pushd /tmp
wget https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-${PLATFORM}.tar.gz
tar xzf s6-overlay-${PLATFORM}.tar.gz -C /
rm s6-overlay-${PLATFORM}.tar.gz
popd
git clone https://github.com/jketterl/js8py.git
pushd js8py
git checkout 888e62be375316882ad2b2ac8e396c3bf857b6fc
python3 setup.py install
popd
rm -rf js8py
git clone https://git.code.sf.net/p/itpp/git itpp
cmakebuild itpp bb5c7e95f40e8fdb5c3f3d01a84bcbaf76f3676d
git clone https://github.com/jketterl/csdr.git
cd csdr
git checkout c4d8a8a5590898e8c9e94b88b96a2fdc7cd0493a
autoreconf -i
./configure
make
make install
cd ..
rm -rf csdr
git clone https://github.com/szechyjs/mbelib.git
cmakebuild mbelib 9a04ed5c78176a9965f3d43f7aa1b1f5330e771f
git clone https://github.com/jketterl/digiham.git
cmakebuild digiham 95206501be89b38d0267bf6c29a6898e7c65656f
git clone https://github.com/f4exb/dsd.git
cmakebuild dsd f6939f9edbbc6f66261833616391a4e59cb2b3d7
JS8CALL_VERSION=2.1.1
JS8CALL_DIR=js8call-${JS8CALL_VERSION}
JS8CALL_TGZ=${JS8CALL_DIR}.tgz
wget http://files.js8call.com/${JS8CALL_VERSION}/${JS8CALL_TGZ}
tar xfz ${JS8CALL_TGZ}
# patch allows us to build against the packaged hamlib
patch -Np1 -d ${JS8CALL_DIR} < /js8call-hamlib.patch
rm /js8call-hamlib.patch
CMAKE_ARGS="-D CMAKE_CXX_FLAGS=-DJS8_USE_LEGACY_HAMLIB" cmakebuild ${JS8CALL_DIR}
rm ${JS8CALL_TGZ}
WSJT_DIR=wsjtx-2.1.2
WSJT_TGZ=${WSJT_DIR}.tgz
wget http://physics.princeton.edu/pulsar/k1jt/${WSJT_TGZ}
tar xfz ${WSJT_TGZ}
patch -Np0 -d ${WSJT_DIR} < /wsjtx-hamlib.patch
mv /wsjtx.patch ${WSJT_DIR}
cmakebuild ${WSJT_DIR}
rm ${WSJT_TGZ}
git clone --depth 1 -b 1.5 https://github.com/wb2osz/direwolf.git
cd direwolf
make
make install
cd ..
rm -rf direwolf
git clone https://github.com/hessu/aprs-symbols /opt/aprs-symbols
pushd /opt/aprs-symbols
git checkout 5c2abe2658ee4d2563f3c73b90c6f59124839802
popd
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean
rm -rf /var/lib/apt/lists/*

View File

@ -1,31 +0,0 @@
#!/bin/bash
set -euo pipefail
mkdir -p /etc/openwebrx/
mkdir -p /tmp/openwebrx/
if [[ ! -f /etc/openwebrx/config_webrx.py ]] ; then
sed 's/temporary_directory = "\/tmp"/temporary_directory = "\/tmp\/openwebrx"/' < "/opt/openwebrx/config_webrx.py" > "/etc/openwebrx/config_webrx.py"
fi
if [[ ! -f /etc/openwebrx/bands.json ]] ; then
cp bands.json /etc/openwebrx/
fi
if [[ ! -f /etc/openwebrx/bookmarks.json ]] ; then
cp bookmarks.json /etc/openwebrx/
fi
if [[ ! -f /etc/openwebrx/users.json ]] ; then
cp users.json /etc/openwebrx/
fi
_term() {
echo "Caught signal!"
kill -TERM "$child" 2>/dev/null
}
trap _term SIGTERM SIGINT
python3 openwebrx.py $@ &
child=$!
wait "$child"

View File

View File

@ -1,14 +0,0 @@
@import url("openwebrx-header.css");
@import url("openwebrx-globals.css");
.buttons {
text-align: right;
}
.row .map-input {
margin: 15px 15px 0;
}
.device {
margin-top: 20px;
}

File diff suppressed because one or more lines are too long

View File

@ -1,7 +0,0 @@
@import url("openwebrx-header.css");
@import url("openwebrx-globals.css");
h1 {
text-align: center;
margin: 50px 0;
}

View File

@ -1,24 +0,0 @@
@import url("openwebrx-header.css");
@import url("openwebrx-globals.css");
.login {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 500px;
padding: 20px;
border-radius: 10px;
border: 1px solid #575757;
box-shadow: 0 0 20px #000;
}
.login .btn {
width: 100%;
}
.btn-login {
height: 50px;
}

View File

@ -1,52 +0,0 @@
@import url("openwebrx-header.css");
@import url("openwebrx-globals.css");
body {
display: flex;
flex-direction: column;
}
#webrx-top-container {
flex: none;
}
.openwebrx-map {
flex: 1 1 auto;
}
h3 {
margin: 10px 0;
text-align: center;
}
ul {
margin-block-start: 5px;
margin-block-end: 5px;
padding-inline-start: 25px;
}
.openwebrx-map-legend {
background-color: #fff;
padding: 10px;
margin: 10px;
}
.openwebrx-map-legend ul {
list-style-type: none;
padding: 0;
}
.openwebrx-map-legend li.square .illustration {
display: inline-block;
width: 30px;
height: 20px;
margin-right: 10px;
border-width: 2px;
border-style: solid;
}
.openwebrx-map-legend select {
background-color: #FFF;
border-color: #DDD;
padding: 5px;
}

View File

@ -1,8 +0,0 @@
html, body
{
margin: 0;
padding: 0;
height: 100%;
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
}

View File

@ -1,195 +0,0 @@
#webrx-top-container
{
position: relative;
z-index:1000;
background-color: #575757;
}
#webrx-top-photo
{
width: 100%;
display: block;
}
#webrx-top-photo-clip
{
min-height: 67px;
max-height: 67px;
height: 350px;
overflow: hidden;
position: relative;
}
.webrx-top-bar-parts
{
height:67px;
}
#webrx-top-bar
{
background: rgba(128, 128, 128, 0.15);
margin:0;
padding:0;
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
right: 0;
}
#webrx-tob-container, #webrx-top-container * {
line-height: initial;
box-sizing: initial;
}
#webrx-top-container img {
vertical-align: initial;
}
#webrx-top-logo
{
padding: 12px;
float: left;
}
#webrx-rx-avatar
{
background-color: rgba(154, 154, 154, .5);
border-radius: 7px;
float: left;
margin: 7px;
cursor:pointer;
width: 46px;
height: 46px;
padding: 4px;
border-radius: 8px;
box-sizing: content-box;
}
#webrx-rx-texts {
float: left;
padding: 10px;
}
#webrx-rx-texts div {
padding: 3px;
}
#webrx-rx-title
{
white-space:nowrap;
overflow: hidden;
cursor:pointer;
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
color: #909090;
font-size: 11pt;
font-weight: bold;
}
#webrx-rx-desc
{
white-space:nowrap;
overflow: hidden;
cursor:pointer;
font-size: 10pt;
color: #909090;
}
#webrx-rx-desc a
{
color: #909090;
}
#openwebrx-rx-details-arrow
{
cursor:pointer;
position: absolute;
left: 470px;
top: 55px;
}
#openwebrx-rx-details-arrow a
{
margin: 0;
padding: 0;
line-height: 0;
display: block;
}
#openwebrx-main-buttons .button {
display: block;
width: 55px;
cursor:pointer;
}
#openwebrx-main-buttons .button img {
height: 38px;
}
#openwebrx-main-buttons a {
color: inherit;
text-decoration: inherit;
}
#openwebrx-main-buttons .button:hover
{
background-color: rgba(255, 255, 255, 0.3);
}
#openwebrx-main-buttons .button:active
{
background-color: rgba(255, 255, 255, 0.55);
}
#openwebrx-main-buttons
{
padding: 5px 15px;
display: flex;
list-style: none;
float: right;
margin:0;
color: white;
text-shadow: 0px 0px 4px #000000;
text-align: center;
font-size: 9pt;
font-weight: bold;
}
#webrx-rx-photo-title
{
position: absolute;
left: 15px;
top: 78px;
color: White;
font-size: 16pt;
text-shadow: 1px 1px 4px #444;
opacity: 1;
}
#webrx-rx-photo-desc
{
position: absolute;
left: 15px;
top: 109px;
color: White;
font-size: 10pt;
font-weight: bold;
text-shadow: 0px 0px 6px #444;
opacity: 1;
line-height: 1.5em;
}
#webrx-rx-photo-desc a
{
color: #5ca8ff;
text-shadow: none;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
<HTML><HEAD>
<TITLE>OpenWebRX Feature report</TITLE>
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico" />
<link rel="stylesheet" href="static/css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="static/css/admin.css" />
<link rel="stylesheet" href="static/css/features.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.0/showdown.min.js"></script>
<script src="static/lib/jquery-3.2.1.min.js"></script>
<script src="static/lib/Header.js"></script>
<script src="static/features.js"></script>
</HEAD><BODY>
${header}
<div class="container">
<h1>OpenWebRX Feature Report</h1>
<table class="features table">
<tr>
<th>Feature</th>
<th>Requirement</th>
<th>Description</th>
<th>Available</th>
</tr>
</table>
</div>
</BODY></HTML>

View File

@ -1,24 +0,0 @@
$(function(){
var converter = new showdown.Converter();
$.ajax('api/features').done(function(data){
var $table = $('table.features');
$.each(data, function(name, details) {
var requirements = $.map(details.requirements, function(r, name){
return '<tr>' +
'<td></td>' +
'<td>' + name + '</td>' +
'<td>' + converter.makeHtml(r.description) + '</td>' +
'<td>' + (r.available ? 'YES' : 'NO') + '</td>' +
'</tr>';
});
$table.append(
'<tr>' +
'<td colspan=2>' + name + '</td>' +
'<td>' + converter.makeHtml(details.description) + '</td>' +
'<td>' + (details.available ? 'YES' : 'NO') + '</td>' +
'</tr>' +
requirements.join("")
);
})
});
});

Binary file not shown.

View File

@ -1,20 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>OpenWebRX Settings</title>
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico" />
<link rel="stylesheet" href="static/css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="static/css/admin.css" />
<script src="https://unpkg.com/location-picker/dist/location-picker.min.js"></script>
<script src="compiled/settings.js"></script>
<meta charset="utf-8">
</head>
<body>
${header}
<div class="container">
<div class="col-12">
<h1>General settings</h1>
</div>
${sections}
</div>
</body>

Binary file not shown.

View File

@ -0,0 +1,93 @@
Copyright (c) 2011, Jasper de Waard (jasper@designtown.nl),
with Reserved Font Name "Expletus Sans".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="5.6444445mm"
height="9.847393mm"
viewBox="0 0 20 34.892337"
id="svg3455"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="Map Pin.svg">
<defs
id="defs3457" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="12.181359"
inkscape:cx="8.4346812"
inkscape:cy="14.715224"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1024"
inkscape:window-height="705"
inkscape:window-x="-4"
inkscape:window-y="-4"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata3460">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-814.59595,-274.38623)">
<g
id="g3477"
transform="matrix(1.1855854,0,0,1.1855854,-151.17715,-57.3976)">
<path
sodipodi:nodetypes="sscccccsscs"
inkscape:connector-curvature="0"
id="path4337-3"
d="m 817.11249,282.97118 c -1.25816,1.34277 -2.04623,3.29881 -2.01563,5.13867 0.0639,3.84476 1.79693,5.3002 4.56836,10.59179 0.99832,2.32851 2.04027,4.79237 3.03125,8.87305 0.13772,0.60193 0.27203,1.16104 0.33416,1.20948 0.0621,0.0485 0.19644,-0.51262 0.33416,-1.11455 0.99098,-4.08068 2.03293,-6.54258 3.03125,-8.87109 2.77143,-5.29159 4.50444,-6.74704 4.56836,-10.5918 0.0306,-1.83986 -0.75942,-3.79785 -2.01758,-5.14062 -1.43724,-1.53389 -3.60504,-2.66908 -5.91619,-2.71655 -2.31115,-0.0475 -4.4809,1.08773 -5.91814,2.62162 z"
style="display:inline;opacity:1;fill:#ff4646;fill-opacity:1;stroke:#d73534;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<circle
r="3.0355"
cy="288.25278"
cx="823.03064"
id="path3049"
style="display:inline;opacity:1;fill:#590000;fill-opacity:1;stroke-width:0" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 B

85
htdocs/inactive.html Normal file
View File

@ -0,0 +1,85 @@
<html>
<!--
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<head><title>OpenWebRX</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
html, body
{
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
width: 100%;
text-align: center;
margin: 0;
padding: 0;
}
img.logo
{
margin-top: 120px;
}
div.frame
{
text-align: left;
margin:0px auto;
width: 800px;
}
div.panel
{
text-align: center;
background-color:#777777;
border-radius: 15px;
padding: 12px;
font-weight: bold;
color: White;
font-size: 13pt;
/*text-shadow: 1px 1px 4px #444;*/
font-family: sans;
}
div.alt
{
font-size: 10pt;
padding-top: 10px;
}
body div a
{
color: #5ca8ff;
text-shadow: none;
}
span.browser
{
}
</style>
</head>
<body>
<div class="frame">
<img class="logo" src="gfx/openwebrx-logo-big.png" style="height: 60px;"/>
<div class="panel">
Sorry, the receiver is inactive due to internal error.
</div>
</div>
</body>
</html>

View File

@ -1,26 +0,0 @@
<div id="webrx-top-container">
<div id="webrx-top-photo-clip">
<img src="static/gfx/openwebrx-top-photo.jpg" id="webrx-top-photo" alt="Receiver panorama"/>
<div id="webrx-top-bar" class="webrx-top-bar-parts">
<a href="https://www.openwebrx.de/" target="_blank"><img src="static/gfx/openwebrx-top-logo.png" id="webrx-top-logo" alt="OpenWebRX Logo"/></a>
<img id="webrx-rx-avatar" class="openwebrx-photo-trigger" src="static/gfx/openwebrx-avatar.png" alt="Receiver avatar"/>
<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>
</div>
<div id="openwebrx-rx-details-arrow">
<a id="openwebrx-rx-details-arrow-up" class="openwebrx-photo-trigger" style="display: none;"><img src="static/gfx/openwebrx-rx-details-arrow-up.png" /></a>
<a id="openwebrx-rx-details-arrow-down" class="openwebrx-photo-trigger"><img src="static/gfx/openwebrx-rx-details-arrow.png" /></a>
</div>
<section id="openwebrx-main-buttons">
<div class="button" data-toggle-panel="openwebrx-panel-status"><img src="static/gfx/openwebrx-panel-status.png" alt="Status"/><br/>Status</div>
<div class="button" data-toggle-panel="openwebrx-panel-log"><img src="static/gfx/openwebrx-panel-log.png" alt="Log"/><br/>Log</div>
<div class="button" data-toggle-panel="openwebrx-panel-receiver"><img src="static/gfx/openwebrx-panel-receiver.png" alt="Receiver"/><br/>Receiver</div>
<a class="button" href="map" target="openwebrx-map"><img src="static/gfx/openwebrx-panel-map.png" alt="Map"/><br/>Map</a>
${settingslink}
</section>
</div>
<div id="webrx-rx-photo-title"></div>
<div id="webrx-rx-photo-desc"></div>
</div>
</div>

View File

@ -1,223 +0,0 @@
<!DOCTYPE HTML>
<!--
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
Copyright (c) 2019-2020 by Jakob Ketterl <dd5jfk@darc.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<html>
<head>
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico" />
<script src="compiled/receiver.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">
</head>
<body onload="openwebrx_init();">
<div id="webrx-page-container">
${header}
<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="webrx-canvas-background">
<div id="webrx-canvas-container">
<!-- add canvas here by javascript -->
</div>
</div>
<div id="openwebrx-panels-container">
<div id="openwebrx-panels-container-left">
<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 openwebrx-message-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 openwebrx-message-panel" id="openwebrx-panel-js8-message" style="display:none; width: 619px;" data-panel-name="js8-message">
<thead><tr>
<th>UTC</th>
<th class="decimal freq">Freq</th>
<th class="message">Message</th>
</tr></thead>
<tbody></tbody>
</table>
<table class="openwebrx-panel openwebrx-message-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>
<table class="openwebrx-panel openwebrx-message-panel" id="openwebrx-panel-pocsag-message" style="display: none; width: 619px;" data-panel-name="pocsag-message">
<thead><tr>
<th class="address">Address</th>
<th class="message">Message</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</div>
<div>
Author contact: <a href="http://www.justjakob.de/" target="_blank">Jakob Ketterl, DD5JFK</a> |
<a href="https://www.openwebrx.de" target="_blank">OpenWebRX homepage</a>
</div>
<div>Support and information: <a href="https://groups.io/g/openwebrx" target="_blank">Groups.io Mailinglist</a></div>
<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" data-type="audiobuffer"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output" data-type="audiooutput"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed" data-type="audiospeed"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed" data-type="networkspeed"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu" data-type="cpu"></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-clients" data-type="clients"></div>
</div>
</div>
<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 class="webrx-actual-freq"></div>
<div class="webrx-mouse-freq"></div>
</div>
<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>
<div class="openwebrx-panel-line">
<select id="openwebrx-sdr-profiles-listbox" onchange="sdr_profile_changed();">
</select>
</div>
<div class="openwebrx-modes openwebrx-panel-line"></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>
<input title="Volume" id="openwebrx-panel-volume" class="openwebrx-panel-slider" type="range" min="0" max="150" value="50" step="1" onchange="updateVolume()" oninput="updateVolume()">
<div title="Auto-adjust waterfall colors" id="openwebrx-waterfall-colors-auto" class="openwebrx-button" onclick="waterfall_measure_minmax_now=true;"><img src="static/gfx/openwebrx-waterfall-auto.png" class="openwebrx-sliderbtn-img"></div>
<input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(0);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div title="Auto-set squelch level" class="openwebrx-squelch-default openwebrx-button"><img src="static/gfx/openwebrx-squelch-button.png" class="openwebrx-sliderbtn-img"></div>
<input title="Squelch" class="openwebrx-squelch-slider openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1">
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><img src="static/gfx/openwebrx-waterfall-default.png" class="openwebrx-sliderbtn-img"></div>
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInOneStep();" title="Zoom in one step"> <img src="static/gfx/openwebrx-zoom-in.png" /></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="static/gfx/openwebrx-zoom-out.png" /></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><img src="static/gfx/openwebrx-zoom-in-total.png" /></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="static/gfx/openwebrx-zoom-out-total.png" /></div>
<div id="openwebrx-smeter-db">0 dB</div>
</div>
<div class="openwebrx-panel-line">
<div id="openwebrx-smeter-outer">
<div id="openwebrx-smeter-bar"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="openwebrx-autoplay-overlay" class="openwebrx-overlay" style="display:none;">
<div class="overlay-content">
<img id="openwebrx-play-button" src="static/gfx/openwebrx-play-button.png" />
<div>Start OpenWebRX</div>
</div>
</div>
<div id="openwebrx-error-overlay" class="openwebrx-overlay" style="display:none;">
<div class="overlay-content">
<div>This receiver is currently unavailable due to technical issues.</div>
<div>Error Message:</div>
<div class="errormessage"></div>
</div>
</div>
<div id="openwebrx-dialog-bookmark" class="openwebrx-dialog" style="display:none;">
<form>
<div class="form-field">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required="required">
</div>
<div class="form-field">
<label for="frequency">Frequency:</label>
<input type="number" id="frequency" name="frequency">
</div>
<div class="form-field">
<label for="modulation">Modulation:</label>
<select name="modulation" id="modulation"></select>
</div>
<div class="buttons">
<div class="openwebrx-button" data-action="cancel">Cancel</div>
<div class="openwebrx-button" data-action="submit">Ok</div>
</div>
<input type="submit" style="display:none;">
</form>
</div>
</body>
</html>

180
htdocs/index.wrx Normal file
View File

@ -0,0 +1,180 @@
<!DOCTYPE HTML>
<!--
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<html>
<head>
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
<script type="text/javascript">
//Global variables
var client_id="%[CLIENT_ID]";
var ws_url="%[WS_URL]";
var rx_photo_height=%[RX_PHOTO_HEIGHT];
var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
var starting_mod="%[START_MOD]";
var starting_offset_frequency = %[START_OFFSET_FREQ];
var waterfall_colors=%[WATERFALL_COLORS];
var waterfall_min_level_default=%[WATERFALL_MIN_LEVEL];
var waterfall_max_level_default=%[WATERFALL_MAX_LEVEL];
var waterfall_auto_level_margin=%[WATERFALL_AUTO_LEVEL_MARGIN];
var server_enable_digimodes=%[DIGIMODES_ENABLE];
var mathbox_waterfall_frequency_resolution=%[MATHBOX_WATERFALL_FRES];
var mathbox_waterfall_history_length=%[MATHBOX_WATERFALL_THIST];
var mathbox_waterfall_colors=%[MATHBOX_WATERFALL_COLORS];
</script>
<script src="sdr.js"></script>
<script src="mathbox-bundle.min.js"></script>
<script src="openwebrx.js"></script>
<script src="jquery-3.2.1.min.js"></script>
<script src="jquery.nanoscroller.js"></script>
<link rel="stylesheet" type="text/css" href="nanoscroller.css" />
<link rel="stylesheet" type="text/css" href="openwebrx.css" />
<meta charset="utf-8">
</head>
<body onload="openwebrx_init();">
<div id="webrx-page-container">
<div id="webrx-top-container">
<div id="webrx-top-photo-clip">
<img src="gfx/openwebrx-top-photo.jpg" id="webrx-top-photo"/>
<div id="webrx-rx-photo-title">%[RX_PHOTO_TITLE]</div>
<div id="webrx-rx-photo-desc">%[RX_PHOTO_DESC]</div>
</div>
<div id="webrx-top-bar-background" class="webrx-top-bar-parts"></div>
<div id="webrx-top-bar" class="webrx-top-bar-parts">
<a href="https://sdr.hu/openwebrx" target="_blank"><img src="gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a>
<a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a>
<img id="webrx-rx-avatar-background" src="gfx/openwebrx-avatar-background.png" onclick="toggle_rx_photo();"/>
<img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/>
<div id="webrx-rx-title" onclick="toggle_rx_photo();">%[RX_TITLE]</div>
<div id="webrx-rx-desc" onclick="toggle_rx_photo();">%[RX_LOC] | Loc: %[RX_QRA], ASL: %[RX_ASL] m, <a href="https://www.google.hu/maps/place/%[RX_GPS]" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a></div>
<div id="openwebrx-rx-details-arrow">
<a id="openwebrx-rx-details-arrow-up" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow-up.png" /></a>
<a id="openwebrx-rx-details-arrow-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a>
</div>
<section id="openwebrx-main-buttons">
<ul>
<li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
<li onmouseup="toggle_panel('openwebrx-panel-log');"><img src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
<li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
</ul>
</section>
</div>
</div>
<div id="webrx-main-container">
<div id="openwebrx-scale-container">
<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
</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="webrx-actual-freq">---.--- MHz</div>
<div id="webrx-mouse-freq">---.--- MHz</div>
<div class="openwebrx-panel-line">
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-nfm"
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>
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-lsb"
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>
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-cw"
onclick="demodulator_analog_replace('cw');">CW</div>
</div>
<div class="openwebrx-panel-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>
<option value="bpsk31">BPSK31</option>
</select>
</div>
<div class="openwebrx-panel-line">
<div title="Mute on/off" id="openwebrx-mute-off" class="openwebrx-button" onclick="toggleMute();"><img src="gfx/openwebrx-speaker.png" class="openwebrx-sliderbtn-img" id="openwebrx-mute-img"></div>
<input title="Volume" id="openwebrx-panel-volume" class="openwebrx-panel-slider" type="range" min="0" max="150" value="50" step="1" onchange="updateVolume()" oninput="updateVolume()">
<div title="Auto-adjust waterfall colors" id="openwebrx-waterfall-colors-auto" class="openwebrx-button" onclick="waterfall_measure_minmax_now=true;"><img src="gfx/openwebrx-waterfall-auto.png" class="openwebrx-sliderbtn-img"></div>
<input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(0);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div title="Auto-set squelch level" id="openwebrx-squelch-default" class="openwebrx-button" onclick="setSquelchToAuto()"><img src="gfx/openwebrx-squelch-button.png" class="openwebrx-sliderbtn-img"></div>
<input title="Squelch" id="openwebrx-panel-squelch" class="openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1" onchange="updateSquelch()" oninput="updateSquelch()">
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><img src="gfx/openwebrx-waterfall-default.png" class="openwebrx-sliderbtn-img"></div>
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInOneStep();" title="Zoom in one step"> <img src="gfx/openwebrx-zoom-in.png" /></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div>
<div class="openwebrx-button openwebrx-square-button" onclick="mathbox_toggle();" title="Toggle 3D view"><img src="gfx/openwebrx-3d-spectrum.png" /></div>
<div id="openwebrx-smeter-db">0 dB</div>
</div>
<div class="openwebrx-panel-line">
<div id="openwebrx-smeter-outer">
<div id="openwebrx-smeter-bar"></div>
</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 <a href="http://blog.sdr.hu/support" target="_blank">donate</a> to say thanks for former development (this is the final version).<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="none" data-panel-order="0" 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="2" 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>
</div>
</div>
</div>
<div id="openwebrx-big-grey" onclick="iosPlayButtonClick();">
<div id="openwebrx-play-button-text">
<img id="openwebrx-play-button" src="gfx/openwebrx-play-button.png" />
<br /><br />Start OpenWebRX
</div>
</div>
</body>
</html>

View File

@ -1,91 +0,0 @@
function AprsMarker() {}
AprsMarker.prototype = new google.maps.OverlayView();
AprsMarker.prototype.draw = function() {
var div = this.div;
var overlay = this.overlay;
if (!div || !overlay) return;
if (this.symbol) {
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';
div.style['background-position-y'] = -Math.floor(this.symbol.index / 16) * 24 + 'px';
}
if (this.course) {
if (this.course > 180) {
div.style.transform = 'scalex(-1) rotate(' + (270 - this.course) + 'deg)'
} else {
div.style.transform = 'rotate(' + (this.course - 90) + 'deg)';
}
} else {
div.style.transform = null;
}
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';
} else {
overlay.style.display = 'none';
}
if (this.opacity) {
div.style.opacity = this.opacity;
} else {
div.style.opacity = null;
}
var point = this.getProjection().fromLatLngToDivPixel(this.position);
if (point) {
div.style.left = point.x - 12 + 'px';
div.style.top = point.y - 12 + 'px';
}
};
AprsMarker.prototype.setOptions = function(options) {
google.maps.OverlayView.prototype.setOptions.apply(this, arguments);
this.draw();
};
AprsMarker.prototype.onAdd = function() {
var div = this.div = document.createElement('div');
div.style.position = 'absolute';
div.style.cursor = 'pointer';
div.style.width = '24px';
div.style.height = '24px';
var overlay = this.overlay = document.createElement('div');
overlay.style.width = '24px';
overlay.style.height = '24px';
overlay.style.background = 'url(aprs-symbols/aprs-symbols-24-2@2x.png)';
overlay.style['background-size'] = '384px 144px';
overlay.style.display = 'none';
div.appendChild(overlay);
var self = this;
google.maps.event.addDomListener(div, "click", function(event) {
event.stopPropagation();
google.maps.event.trigger(self, "click", event);
});
var panes = this.getPanes();
panes.overlayImage.appendChild(div);
};
AprsMarker.prototype.remove = function() {
if (this.div) {
this.div.parentNode.removeChild(this.div);
this.div = null;
}
};
AprsMarker.prototype.getAnchorPoint = function() {
return new google.maps.Point(0, -12);
};

View File

@ -1,356 +0,0 @@
// 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 ImaAdpcmCodec();
this.compression = 'none';
this.setupResampling();
this.resampler = new Interpolator(this.resamplingFactor);
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(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);
};
function ImaAdpcmCodec() {
this.reset();
}
ImaAdpcmCodec.prototype.reset = function() {
this.stepIndex = 0;
this.predictor = 0;
this.step = 0;
};
ImaAdpcmCodec.imaIndexTable = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 ];
ImaAdpcmCodec.imaStepTable = [
7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
];
ImaAdpcmCodec.prototype.decode = function(data) {
var output = new Int16Array(data.length * 2);
for (var i = 0; i < data.length; i++) {
output[i * 2] = this.decodeNibble(data[i] & 0x0F);
output[i * 2 + 1] = this.decodeNibble((data[i] >> 4) & 0x0F);
}
return output;
};
ImaAdpcmCodec.prototype.decodeNibble = function(nibble) {
this.stepIndex += ImaAdpcmCodec.imaIndexTable[nibble];
this.stepIndex = Math.min(Math.max(this.stepIndex, 0), 88);
var diff = this.step >> 3;
if (nibble & 1) diff += this.step >> 2;
if (nibble & 2) diff += this.step >> 1;
if (nibble & 4) diff += this.step;
if (nibble & 8) diff = -diff;
this.predictor += diff;
this.predictor = Math.min(Math.max(this.predictor, -32768), 32767);
this.step = ImaAdpcmCodec.imaStepTable[this.stepIndex];
return this.predictor;
};
function Interpolator(factor) {
this.factor = factor;
this.lowpass = new Lowpass(factor)
}
Interpolator.prototype.process = function(data) {
var output = new Float32Array(data.length * this.factor);
for (var i = 0; i < data.length; i++) {
output[i * this.factor] = (data[i] + 0.5) / 32768;
}
return this.lowpass.process(output);
};
function Lowpass(interpolation) {
this.interpolation = interpolation;
var transitionBandwidth = 0.05;
this.numtaps = Math.round(4 / transitionBandwidth);
if (this.numtaps % 2 == 0) this.numtaps += 1;
var cutoff = 1 / interpolation;
this.coefficients = this.getCoefficients(cutoff / 2);
this.delay = new Float32Array(this.numtaps);
for (var i = 0; i < this.numtaps; i++){
this.delay[i] = 0;
}
this.delayIndex = 0;
}
Lowpass.prototype.getCoefficients = function(cutoffRate) {
var middle = Math.floor(this.numtaps / 2);
// hamming window
var window_function = function(r){
var rate = 0.5 + r / 2;
return 0.54 - 0.46 * Math.cos(2 * Math.PI * rate);
}
var output = [];
output[middle] = 2 * Math.PI * cutoffRate * window_function(0);
for (var i = 1; i <= middle; i++) {
output[middle - i] = output[middle + i] = (Math.sin(2 * Math.PI * cutoffRate * i) / i) * window_function(i / middle);
}
return this.normalizeCoefficients(output);
};
Lowpass.prototype.normalizeCoefficients = function(input) {
var sum = 0;
var output = [];
for (var i = 0; i < input.length; i++) {
sum += input[i];
}
for (var i = 0; i < input.length; i++) {
output[i] = input[i] / sum;
}
return output;
};
Lowpass.prototype.process = function(input) {
output = new Float32Array(input.length);
for (var oi = 0; oi < input.length; oi++) {
this.delay[this.delayIndex] = input[oi];
this.delayIndex = (this.delayIndex + 1) % this.numtaps;
var acc = 0;
var index = this.delayIndex;
for (var i = 0; i < this.numtaps; ++i) {
var index = index != 0 ? index - 1 : this.numtaps - 1;
acc += this.delay[index] * this.coefficients[i];
if (isNaN(acc)) debugger;
}
// gain by interpolation
output[oi] = this.interpolation * acc;
}
return output;
};

View File

@ -1,58 +0,0 @@
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);

View File

@ -1,165 +0,0 @@
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) return;
me.getDemodulator().set_offset_frequency(b.frequency - center_freq);
if (b.modulation) {
me.getDemodulatorPanel().setMode(b.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) {
if (!bookmark) {
bookmark = {
name: "",
frequency: center_freq + this.getDemodulator().get_offset_frequency(),
modulation: this.getDemodulator().get_secondary_demod() || this.getDemodulator().get_modulation()
}
}
this.$dialog.bookmarkDialog().setValues(bookmark);
this.$dialog.show();
this.$dialog.find('#name').focus();
};
BookmarkBar.prototype.storeBookmark = function() {
var me = this;
var bookmark = this.$dialog.bookmarkDialog().getValues();
if (!bookmark) return;
bookmark.frequency = Number(bookmark.frequency);
var bookmarks = me.localBookmarks.getBookmarks();
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();
};
BookmarkBar.prototype.getDemodulatorPanel = function() {
return $('#openwebrx-panel-receiver').demodulatorPanel();
};
BookmarkBar.prototype.getDemodulator = function() {
return this.getDemodulatorPanel().getDemodulator();
};
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);
};

View File

@ -1,36 +0,0 @@
$.fn.bookmarkDialog = function() {
var $el = this;
return {
setModes: function(modes) {
$el.find('#modulation').html(modes.filter(function(m){
return m.isAvailable();
}).map(function(m) {
return '<option value="' + m.modulation + '">' + m.name + '</option>';
}).join(''));
return this;
},
setValues: function(bookmark) {
var $form = $el.find('form');
['name', 'frequency', 'modulation'].forEach(function(key){
$form.find('#' + key).val(bookmark[key]);
});
$el.data('id', bookmark.id || false);
return this;
},
getValues: function() {
var bookmark = {};
var valid = true;
['name', 'frequency', 'modulation'].forEach(function(key){
var $input = $el.find('#' + key);
valid = valid && $input[0].checkValidity();
bookmark[key] = $input.val();
});
if (!valid) {
$el.find("form :submit").click();
return;
}
bookmark.id = $el.data('id');
return bookmark;
}
}
}

View File

@ -1,356 +0,0 @@
function Filter(demodulator) {
this.demodulator = demodulator;
this.min_passband = 100;
}
Filter.prototype.getLimits = function() {
var max_bw;
if (this.demodulator.get_secondary_demod() === 'pocsag') {
max_bw = 12500;
} else {
max_bw = (audioEngine.getOutputRate() / 2) - 1;
}
return {
high: max_bw,
low: -max_bw
};
};
function Envelope(demodulator) {
this.demodulator = demodulator;
this.dragged_range = Demodulator.draggable_ranges.none;
}
Envelope.prototype.draw = function(visible_range){
this.visible_range = visible_range;
var line = center_freq + this.demodulator.offset_frequency;
// ____
// Draws a standard filter envelope like this: _/ \_
// Parameters are given in offset frequency (Hz).
// Envelope is drawn on the scale canvas.
// A "drag range" object is returned, containing information about the draggable areas of the envelope
// (beginning, ending and the line showing the offset frequency).
var env_bounding_line_w = 5; //
var env_att_w = 5; // _______ ___env_h2 in px ___|_____
var env_h1 = 17; // _/| \_ ___env_h1 in px _/ |_ \_
var env_h2 = 5; // |||env_att_line_w |_env_lineplus
var env_lineplus = 1; // ||env_bounding_line_w
var env_line_click_area = 6;
//range=get_visible_freq_range();
var from = center_freq + this.demodulator.offset_frequency + this.demodulator.low_cut;
var from_px = scale_px_from_freq(from, range);
var to = center_freq + this.demodulator.offset_frequency + this.demodulator.high_cut;
var to_px = scale_px_from_freq(to, range);
if (to_px < from_px) /* swap'em */ {
var temp_px = to_px;
to_px = from_px;
from_px = temp_px;
}
from_px -= (env_att_w + env_bounding_line_w);
to_px += (env_att_w + env_bounding_line_w);
// do drawing:
scale_ctx.lineWidth = 3;
var color = this.color || '#ffff00'; // yellow
scale_ctx.strokeStyle = color;
scale_ctx.fillStyle = color;
var drag_ranges = {envelope_on_screen: false, line_on_screen: false};
if (!(to_px < 0 || from_px > window.innerWidth)) // out of screen?
{
drag_ranges.beginning = {x1: from_px, x2: from_px + env_bounding_line_w + env_att_w};
drag_ranges.ending = {x1: to_px - env_bounding_line_w - env_att_w, x2: to_px};
drag_ranges.whole_envelope = {x1: from_px, x2: to_px};
drag_ranges.envelope_on_screen = true;
scale_ctx.beginPath();
scale_ctx.moveTo(from_px, env_h1);
scale_ctx.lineTo(from_px + env_bounding_line_w, env_h1);
scale_ctx.lineTo(from_px + env_bounding_line_w + env_att_w, env_h2);
scale_ctx.lineTo(to_px - env_bounding_line_w - env_att_w, env_h2);
scale_ctx.lineTo(to_px - env_bounding_line_w, env_h1);
scale_ctx.lineTo(to_px, env_h1);
scale_ctx.globalAlpha = 0.3;
scale_ctx.fill();
scale_ctx.globalAlpha = 1;
scale_ctx.stroke();
}
if (typeof line !== "undefined") // out of screen?
{
var line_px = scale_px_from_freq(line, range);
if (!(line_px < 0 || line_px > window.innerWidth)) {
drag_ranges.line = {x1: line_px - env_line_click_area / 2, x2: line_px + env_line_click_area / 2};
drag_ranges.line_on_screen = true;
scale_ctx.moveTo(line_px, env_h1 + env_lineplus);
scale_ctx.lineTo(line_px, env_h2 - env_lineplus);
scale_ctx.stroke();
}
}
this.drag_ranges = drag_ranges;
};
Envelope.prototype.drag_start = function(x, key_modifiers){
this.key_modifiers = key_modifiers;
this.dragged_range = this.where_clicked(x, this.drag_ranges, key_modifiers);
this.drag_origin = {
x: x,
low_cut: this.demodulator.low_cut,
high_cut: this.demodulator.high_cut,
offset_frequency: this.demodulator.offset_frequency
};
return this.dragged_range !== Demodulator.draggable_ranges.none;
};
Envelope.prototype.where_clicked = function(x, drag_ranges, key_modifiers) { // Check exactly what the user has clicked based on ranges returned by envelope_draw().
var in_range = function (x, range) {
return range.x1 <= x && range.x2 >= x;
};
var dr = Demodulator.draggable_ranges;
if (key_modifiers.shiftKey) {
//Check first: shift + center drag emulates BFO knob
if (drag_ranges.line_on_screen && in_range(x, drag_ranges.line)) return dr.bfo;
//Check second: shift + envelope drag emulates PBF knob
if (drag_ranges.envelope_on_screen && in_range(x, drag_ranges.whole_envelope)) return dr.pbs;
}
if (drag_ranges.envelope_on_screen) {
// For low and high cut:
if (in_range(x, drag_ranges.beginning)) return dr.beginning;
if (in_range(x, drag_ranges.ending)) return dr.ending;
// Last priority: having clicked anything else on the envelope, without holding the shift key
if (in_range(x, drag_ranges.whole_envelope)) return dr.anything_else;
}
return dr.none; //User doesn't drag the envelope for this demodulator
};
Envelope.prototype.drag_move = function(x) {
var dr = Demodulator.draggable_ranges;
var new_value;
if (this.dragged_range === dr.none) return false; // we return if user is not dragging (us) at all
var freq_change = Math.round(this.visible_range.hps * (x - this.drag_origin.x));
//dragging the line in the middle of the filter envelope while holding Shift does emulate
//the BFO knob on radio equipment: moving offset frequency, while passband remains unchanged
//Filter passband moves in the opposite direction than dragged, hence the minus below.
var minus = (this.dragged_range === dr.bfo) ? -1 : 1;
//dragging any other parts of the filter envelope while holding Shift does emulate the PBS knob
//(PassBand Shift) on radio equipment: PBS does move the whole passband without moving the offset
//frequency.
if (this.dragged_range === dr.beginning || this.dragged_range === dr.bfo || this.dragged_range === dr.pbs) {
//we don't let low_cut go beyond its limits
if ((new_value = this.drag_origin.low_cut + minus * freq_change) < this.demodulator.filter.getLimits().low) return true;
//nor the filter passband be too small
if (this.demodulator.high_cut - new_value < this.demodulator.filter.min_passband) return true;
//sanity check to prevent GNU Radio "firdes check failed: fa <= fb"
if (new_value >= this.demodulator.high_cut) return true;
this.demodulator.setLowCut(new_value);
}
if (this.dragged_range === dr.ending || this.dragged_range === dr.bfo || this.dragged_range === dr.pbs) {
//we don't let high_cut go beyond its limits
if ((new_value = this.drag_origin.high_cut + minus * freq_change) > this.demodulator.filter.getLimits().high) return true;
//nor the filter passband be too small
if (new_value - this.demodulator.low_cut < this.demodulator.filter.min_passband) return true;
//sanity check to prevent GNU Radio "firdes check failed: fa <= fb"
if (new_value <= this.demodulator.low_cut) return true;
this.demodulator.setHighCut(new_value);
}
if (this.dragged_range === dr.anything_else || this.dragged_range === dr.bfo) {
//when any other part of the envelope is dragged, the offset frequency is changed (whole passband also moves with it)
new_value = this.drag_origin.offset_frequency + freq_change;
if (new_value > bandwidth / 2 || new_value < -bandwidth / 2) return true; //we don't allow tuning above Nyquist frequency :-)
this.demodulator.set_offset_frequency(new_value);
}
//now do the actual modifications:
//mkenvelopes(this.visible_range);
//this.demodulator.set();
return true;
};
Envelope.prototype.drag_end = function(){
var to_return = this.dragged_range !== Demodulator.draggable_ranges.none; //this part is required for cliking anywhere on the scale to set offset
this.dragged_range = Demodulator.draggable_ranges.none;
return to_return;
};
//******* class Demodulator_default_analog *******
// This can be used as a base for basic audio demodulators.
// It already supports most basic modulations used for ham radio and commercial services: AM/FM/LSB/USB
function Demodulator(offset_frequency, modulation) {
this.offset_frequency = offset_frequency;
this.envelope = new Envelope(this);
this.color = Demodulator.get_next_color();
this.modulation = modulation;
this.filter = new Filter(this);
this.squelch_level = -150;
this.dmr_filter = 3;
this.started = false;
this.state = {};
this.secondary_demod = false;
var mode = Modes.findByModulation(modulation);
if (mode) {
this.low_cut = mode.bandpass.low_cut;
this.high_cut = mode.bandpass.high_cut;
}
this.listeners = {
"frequencychange": [],
"squelchchange": []
};
}
//ranges on filter envelope that can be dragged:
Demodulator.draggable_ranges = {
none: 0,
beginning: 1 /*from*/,
ending: 2 /*to*/,
anything_else: 3,
bfo: 4 /*line (while holding shift)*/,
pbs: 5
}; //to which parameter these correspond in envelope_draw()
Demodulator.color_index = 0;
Demodulator.colors = ["#ffff00", "#00ff00", "#00ffff", "#058cff", "#ff9600", "#a1ff39", "#ff4e39", "#ff5dbd"];
Demodulator.get_next_color = function() {
if (this.color_index >= this.colors.length) this.color_index = 0;
return (this.colors[this.color_index++]);
}
Demodulator.prototype.on = function(event, handler) {
this.listeners[event].push(handler);
};
Demodulator.prototype.emit = function(event, params) {
this.listeners[event].forEach(function(fn) {
fn(params);
});
};
Demodulator.prototype.set_offset_frequency = function(to_what) {
if (to_what > bandwidth / 2 || to_what < -bandwidth / 2) return;
to_what = Math.round(to_what);
if (this.offset_frequency === to_what) {
return;
}
this.offset_frequency = to_what;
this.set();
this.emit("frequencychange", to_what);
mkenvelopes(get_visible_freq_range());
};
Demodulator.prototype.get_offset_frequency = function() {
return this.offset_frequency;
};
Demodulator.prototype.get_modulation = function() {
return this.modulation;
};
Demodulator.prototype.start = function() {
this.started = true;
this.set();
ws.send(JSON.stringify({
"type": "dspcontrol",
"action": "start"
}));
};
// TODO check if this is actually used
Demodulator.prototype.stop = function() {
};
Demodulator.prototype.send = function(params) {
ws.send(JSON.stringify({"type": "dspcontrol", "params": params}));
}
Demodulator.prototype.set = function () { //this function sends demodulator parameters to the server
if (!this.started) return;
var params = {
"low_cut": this.low_cut,
"high_cut": this.high_cut,
"offset_freq": this.offset_frequency,
"mod": this.modulation,
"dmr_filter": this.dmr_filter,
"squelch_level": this.squelch_level,
"secondary_mod": this.secondary_demod,
"secondary_offset_freq": this.secondary_offset_freq
};
var to_send = {};
for (var key in params) {
if (!(key in this.state) || params[key] !== this.state[key]) {
to_send[key] = params[key];
}
}
if (Object.keys(to_send).length > 0) {
this.send(to_send);
for (var key in to_send) {
this.state[key] = to_send[key];
}
}
mkenvelopes(get_visible_freq_range());
};
Demodulator.prototype.setSquelch = function(squelch) {
if (this.squelch_level == squelch) {
return;
}
this.squelch_level = squelch;
this.set();
this.emit("squelchchange", squelch);
};
Demodulator.prototype.getSquelch = function() {
return this.squelch_level;
};
Demodulator.prototype.setDmrFilter = function(dmr_filter) {
this.dmr_filter = dmr_filter;
this.set();
};
Demodulator.prototype.setBandpass = function(bandpass) {
this.bandpass = bandpass;
this.low_cut = bandpass.low_cut;
this.high_cut = bandpass.high_cut;
this.set();
};
Demodulator.prototype.setLowCut = function(low_cut) {
this.low_cut = low_cut;
this.set();
};
Demodulator.prototype.setHighCut = function(high_cut) {
this.high_cut = high_cut;
this.set();
};
Demodulator.prototype.getBandpass = function() {
return {
low_cut: this.low_cut,
high_cut: this.high_cut
};
};
Demodulator.prototype.set_secondary_demod = function(secondary_demod) {
if (this.secondary_demod === secondary_demod) {
return;
}
this.secondary_demod = secondary_demod;
this.set();
};
Demodulator.prototype.get_secondary_demod = function() {
return this.secondary_demod;
};
Demodulator.prototype.set_secondary_offset_freq = function(secondary_offset) {
if (this.secondary_offset_freq === secondary_offset) {
return;
}
this.secondary_offset_freq = secondary_offset;
this.set();
};

View File

@ -1,333 +0,0 @@
function DemodulatorPanel(el) {
var self = this;
self.el = el;
self.demodulator = null;
self.mode = null;
var displayEl = el.find('.webrx-actual-freq')
this.tuneableFrequencyDisplay = displayEl.tuneableFrequencyDisplay();
displayEl.on('frequencychange', function(event, freq) {
self.getDemodulator().set_offset_frequency(freq - self.center_freq);
});
Modes.registerModePanel(this);
el.on('click', '.openwebrx-demodulator-button', function() {
var modulation = $(this).data('modulation');
if (modulation) {
self.setMode(modulation);
} else {
self.disableDigiMode();
}
});
el.on('change', '.openwebrx-secondary-demod-listbox', function() {
var value = $(this).val();
if (value === 'none') {
self.disableDigiMode();
} else {
self.setMode(value);
}
});
el.on('click', '.openwebrx-squelch-default', function() {
if (!self.squelchAvailable()) return;
el.find('.openwebrx-squelch-slider').val(getLogSmeterValue(smeter_level) + 10);
self.updateSquelch();
});
el.on('change', '.openwebrx-squelch-slider', function() {
self.updateSquelch();
});
window.addEventListener('hashchange', function() {
self.onHashChange();
});
};
DemodulatorPanel.prototype.render = function() {
var available = Modes.getModes().filter(function(m){ return m.isAvailable(); });
var normalModes = available.filter(function(m){ return m.type === 'analog'; });
var digiModes = available.filter(function(m){ return m.type === 'digimode'; });
var html = []
var buttons = normalModes.map(function(m){
return $(
'<div ' +
'class="openwebrx-button openwebrx-demodulator-button" ' +
'data-modulation="' + m.modulation + '" ' +
'id="openwebrx-button-' + m.modulation + '" r' +
'>' + m.name + '</div>'
);
});
var index = 0;
var arrayLength = buttons.length;
var chunks = [];
for (index = 0; index < arrayLength; index += 5) {
chunks.push(buttons.slice(index, index + 5));
}
html.push.apply(html, chunks.map(function(chunk){
$line = $('<div class="openwebrx-panel-line openwebrx-panel-flex-line"></div>');
$line.append.apply($line, chunk);
return $line
}));
html.push($(
'<div class="openwebrx-panel-line openwebrx-panel-flex-line">' +
'<div class="openwebrx-button openwebrx-demodulator-button openwebrx-button-dig">DIG</div>' +
'<select class="openwebrx-secondary-demod-listbox">' +
'<option value="none"></option>' +
digiModes.map(function(m){
return '<option value="' + m.modulation + '">' + m.name + '</option>';
}).join('') +
'</select>' +
'</div>'
));
this.el.find(".openwebrx-modes").html(html);
};
DemodulatorPanel.prototype.setMode = function(requestedModulation) {
var mode = Modes.findByModulation(requestedModulation);
if (!mode) {
return;
}
if (this.mode === mode) {
return;
}
if (!mode.isAvailable()) {
divlog('Modulation "' + mode.name + '" not supported. Please check requirements', true);
return;
}
if (mode.type === 'digimode') {
modulation = mode.underlying[0];
} else {
if (this.mode && this.mode.type === 'digimode' && this.mode.underlying.indexOf(requestedModulation) >= 0) {
// keep the mode, just switch underlying modulation
mode = this.mode;
modulation = requestedModulation;
} else {
modulation = mode.modulation;
}
}
var current = this.collectParams();
if (this.demodulator) {
current.offset_frequency = this.demodulator.get_offset_frequency();
current.squelch_level = this.demodulator.getSquelch();
}
this.stopDemodulator();
this.demodulator = new Demodulator(current.offset_frequency, modulation);
this.demodulator.setSquelch(current.squelch_level);
var self = this;
var updateFrequency = function(freq) {
self.tuneableFrequencyDisplay.setFrequency(self.center_freq + freq);
self.updateHash();
};
this.demodulator.on("frequencychange", updateFrequency);
updateFrequency(this.demodulator.get_offset_frequency());
var updateSquelch = function(squelch) {
self.el.find('.openwebrx-squelch-slider').val(squelch);
self.updateHash();
};
this.demodulator.on('squelchchange', updateSquelch);
updateSquelch(this.demodulator.getSquelch());
if (mode.type === 'digimode') {
this.demodulator.set_secondary_demod(mode.modulation);
if (mode.bandpass) {
this.demodulator.setBandpass(mode.bandpass);
}
} else {
this.demodulator.set_secondary_demod(false);
}
this.demodulator.start();
this.mode = mode;
this.updateButtons();
this.updatePanels();
this.updateHash();
};
DemodulatorPanel.prototype.disableDigiMode = function() {
// just a little trick to get out of the digimode
delete this.mode;
this.setMode(this.getDemodulator().get_modulation());
};
DemodulatorPanel.prototype.updatePanels = function() {
var modulation = this.getDemodulator().get_secondary_demod();
$('#openwebrx-panel-digimodes').attr('data-mode', modulation);
toggle_panel("openwebrx-panel-digimodes", !!modulation);
toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4'].indexOf(modulation) >= 0);
toggle_panel("openwebrx-panel-js8-message", modulation == "js8");
toggle_panel("openwebrx-panel-packet-message", modulation === "packet");
toggle_panel("openwebrx-panel-pocsag-message", modulation === "pocsag");
modulation = this.getDemodulator().get_modulation();
var showing = 'openwebrx-panel-metadata-' + modulation;
$(".openwebrx-meta-panel").each(function (_, p) {
toggle_panel(p.id, p.id === showing);
});
clear_metadata();
};
DemodulatorPanel.prototype.getDemodulator = function() {
return this.demodulator;
};
DemodulatorPanel.prototype.collectParams = function() {
var defaults = {
offset_frequency: 0,
squelch_level: -150,
mod: 'nfm'
}
return $.extend(new Object(), defaults, this.initialParams || {}, this.transformHashParams(this.parseHash()));
};
DemodulatorPanel.prototype.startDemodulator = function() {
if (!Modes.initComplete()) return;
var params = this.collectParams();
this._apply(params);
};
DemodulatorPanel.prototype.stopDemodulator = function() {
if (!this.demodulator) {
return;
}
this.demodulator.stop();
this.demodulator = null;
this.mode = null;
}
DemodulatorPanel.prototype._apply = function(params) {
this.setMode(params.mod);
this.getDemodulator().set_offset_frequency(params.offset_frequency);
this.getDemodulator().setSquelch(params.squelch_level);
this.updateButtons();
};
DemodulatorPanel.prototype.setInitialParams = function(params) {
this.initialParams = params;
};
DemodulatorPanel.prototype.onHashChange = function() {
this._apply(this.transformHashParams(this.parseHash()));
};
DemodulatorPanel.prototype.transformHashParams = function(params) {
var ret = {
mod: params.secondary_mod || params.mod
};
if (typeof(params.offset_frequency) !== 'undefined') ret.offset_frequency = params.offset_frequency;
if (typeof(params.sql) !== 'undefined') ret.squelch_level = parseInt(params.sql);
return ret;
};
DemodulatorPanel.prototype.squelchAvailable = function () {
return this.mode && this.mode.squelch;
}
DemodulatorPanel.prototype.updateButtons = function() {
var $buttons = this.el.find(".openwebrx-demodulator-button");
$buttons.removeClass("highlighted").removeClass('same-mod');
var demod = this.getDemodulator()
if (!demod) return;
this.el.find('[data-modulation=' + demod.get_modulation() + ']').addClass("highlighted");
var secondary_demod = demod.get_secondary_demod()
if (secondary_demod) {
this.el.find(".openwebrx-button-dig").addClass("highlighted");
this.el.find('.openwebrx-secondary-demod-listbox').val(secondary_demod);
var mode = Modes.findByModulation(secondary_demod);
if (mode) {
var self = this;
mode.underlying.filter(function(m) {
return m !== demod.get_modulation();
}).forEach(function(m) {
self.el.find('[data-modulation=' + m + ']').addClass('same-mod')
});
}
} else {
this.el.find('.openwebrx-secondary-demod-listbox').val('none');
}
var squelch_disabled = !this.squelchAvailable();
this.el.find('.openwebrx-squelch-slider').prop('disabled', squelch_disabled);
this.el.find('.openwebrx-squelch-default')[squelch_disabled ? 'addClass' : 'removeClass']('disabled');
}
DemodulatorPanel.prototype.setCenterFrequency = function(center_freq) {
if (this.center_freq === center_freq) {
return;
}
this.stopDemodulator();
this.center_freq = center_freq;
this.startDemodulator();
};
DemodulatorPanel.prototype.parseHash = function() {
if (!window.location.hash) {
return {};
}
var params = window.location.hash.substring(1).split(",").map(function(x) {
var harr = x.split('=');
return [harr[0], harr.slice(1).join('=')];
}).reduce(function(params, p){
params[p[0]] = p[1];
return params;
}, {});
return this.validateHash(params);
};
DemodulatorPanel.prototype.validateHash = function(params) {
var self = this;
params = Object.keys(params).filter(function(key) {
if (key == 'freq' || key == 'mod' || key == 'secondary_mod' || key == 'sql') {
return params.freq && Math.abs(params.freq - self.center_freq) < bandwidth;
}
return true;
}).reduce(function(p, key) {
p[key] = params[key];
return p;
}, {});
if (params['freq']) {
params['offset_frequency'] = params['freq'] - self.center_freq;
delete params['freq'];
}
return params;
};
DemodulatorPanel.prototype.updateHash = function() {
var demod = this.getDemodulator();
if (!demod) return;
var self = this;
window.location.hash = $.map({
freq: demod.get_offset_frequency() + self.center_freq,
mod: demod.get_modulation(),
secondary_mod: demod.get_secondary_demod(),
sql: demod.getSquelch(),
}, function(value, key){
if (typeof(value) === 'undefined' || value === false) return undefined;
return key + '=' + value;
}).filter(function(v) {
return !!v;
}).join(',');
};
DemodulatorPanel.prototype.updateSquelch = function() {
var sliderValue = parseInt(this.el.find(".openwebrx-squelch-slider").val());
var demod = this.getDemodulator();
if (demod) demod.setSquelch(sliderValue);
};
$.fn.demodulatorPanel = function(){
if (!this.data('panel')) {
this.data('panel', new DemodulatorPanel(this));
};
return this.data('panel');
};

View File

@ -1,106 +0,0 @@
function FrequencyDisplay(element) {
this.element = $(element);
this.digits = [];
this.setupElements();
this.setFrequency(0);
}
FrequencyDisplay.prototype.setupElements = function() {
this.displayContainer = $('<div>');
this.digitContainer = $('<span>');
this.displayContainer.html([this.digitContainer, $('<span> MHz</span>')]);
this.element.html(this.displayContainer);
};
FrequencyDisplay.prototype.setFrequency = function(freq) {
this.frequency = freq;
var formatted = (freq / 1e6).toLocaleString(undefined, {maximumFractionDigits: 4, minimumFractionDigits: 4});
var children = this.digitContainer.children();
for (var i = 0; i < formatted.length; i++) {
if (!this.digits[i]) {
this.digits[i] = $('<span>');
var before = children[i];
if (before) {
$(before).after(this.digits[i]);
} else {
this.digitContainer.append(this.digits[i]);
}
}
this.digits[i][(isNaN(formatted[i]) ? 'remove' : 'add') + 'Class']('digit');
this.digits[i].html(formatted[i]);
}
while (this.digits.length > formatted.length) {
this.digits.pop().remove();
}
};
function TuneableFrequencyDisplay(element) {
FrequencyDisplay.call(this, element);
this.setupEvents();
}
TuneableFrequencyDisplay.prototype = new FrequencyDisplay();
TuneableFrequencyDisplay.prototype.setupElements = function() {
FrequencyDisplay.prototype.setupElements.call(this);
this.input = $('<input>');
this.input.hide();
this.element.append(this.input);
};
TuneableFrequencyDisplay.prototype.setupEvents = function() {
var me = this;
me.element.on('wheel', function(e){
e.preventDefault();
e.stopPropagation();
var index = me.digitContainer.find('.digit').index(e.target);
if (index < 0) return;
var delta = 10 ** (Math.floor(Math.log10(me.frequency)) - index);
if (e.originalEvent.deltaY > 0) delta *= -1;
var newFrequency = me.frequency + delta;
me.element.trigger('frequencychange', newFrequency);
});
var submit = function(){
var freq = parseInt(me.input.val());
if (!isNaN(freq)) {
me.element.trigger('frequencychange', freq);
}
me.input.hide();
me.displayContainer.show();
};
me.input.on('blur', submit).on('keyup', function(e){
if (e.keyCode == 13) return submit();
if (e.keyCode == 27) {
me.input.hide();
me.displayContainer.show();
}
});
me.input.on('click', function(e){
e.stopPropagation();
});
me.element.on('click', function(){
me.input.val(me.frequency);
me.input.show();
me.displayContainer.hide();
me.input.focus();
});
};
$.fn.frequencyDisplay = function() {
if (!this.data('frequencyDisplay')) {
this.data('frequencyDisplay', new FrequencyDisplay(this));
}
return this.data('frequencyDisplay');
}
$.fn.tuneableFrequencyDisplay = function() {
if (!this.data('frequencyDisplay')) {
this.data('frequencyDisplay', new TuneableFrequencyDisplay(this));
}
return this.data('frequencyDisplay');
}

View File

@ -1,77 +0,0 @@
function Header(el) {
this.el = el;
this.el.find('#openwebrx-main-buttons').find('[data-toggle-panel]').click(function () {
toggle_panel($(this).data('toggle-panel'));
});
this.init_rx_photo();
this.download_details();
};
Header.prototype.setDetails = function(details) {
this.el.find('#webrx-rx-title').html(details['receiver_name']);
var query = encodeURIComponent(details['receiver_gps']['lat'] + ',' + details['receiver_gps']['lon']);
this.el.find('#webrx-rx-desc').html(details['receiver_location'] + ' | Loc: ' + details['locator'] + ', ASL: ' + details['receiver_asl'] + ' m, <a href="https://www.google.com/maps/search/?api=1&query=' + query + '" target="_blank">[maps]</a>');
this.el.find('#webrx-rx-photo-title').html(details['photo_title']);
this.el.find('#webrx-rx-photo-desc').html(details['photo_desc']);
};
Header.prototype.init_rx_photo = function() {
this.rx_photo_state = 0;
$.extend($.easing, {
easeOutCubic:function(x) {
return 1 - Math.pow( 1 - x, 3 );
}
});
$('#webrx-top-container').find('.openwebrx-photo-trigger').click(this.toggle_rx_photo.bind(this));
};
Header.prototype.close_rx_photo = function() {
this.rx_photo_state = 0;
this.el.find("#webrx-rx-photo-desc").animate({opacity: 0});
this.el.find("#webrx-rx-photo-title").animate({opacity: 0});
this.el.find('#webrx-top-photo-clip').animate({maxHeight: 67}, {duration: 1000, easing: 'easeOutCubic'});
this.el.find("#openwebrx-rx-details-arrow-down").show();
this.el.find("#openwebrx-rx-details-arrow-up").hide();
}
Header.prototype.open_rx_photo = function() {
this.rx_photo_state = 1;
this.el.find("#webrx-rx-photo-desc").animate({opacity: 1});
this.el.find("#webrx-rx-photo-title").animate({opacity: 1});
this.el.find('#webrx-top-photo-clip').animate({maxHeight: 350}, {duration: 1000, easing: 'easeOutCubic'});
this.el.find("#openwebrx-rx-details-arrow-down").hide();
this.el.find("#openwebrx-rx-details-arrow-up").show();
}
Header.prototype.toggle_rx_photo = function(ev) {
if (ev && ev.target && ev.target.tagName == 'A') {
return;
}
if (this.rx_photo_state) {
this.close_rx_photo();
} else {
this.open_rx_photo();
}
};
Header.prototype.download_details = function() {
var self = this;
$.ajax('api/receiverdetails').done(function(data){
self.setDetails(data);
});
};
$.fn.header = function() {
if (!this.data('header')) {
this.data('header', new Header(this));
}
return this.data('header');
};
$(function(){
$('#webrx-top-container').header();
});

View File

@ -1,150 +0,0 @@
Js8Thread = function(el){
this.messages = [];
this.el = el;
};
Js8Thread.prototype.getAverageFrequency = function(){
var total = this.messages.map(function(message){
return message.freq;
}).reduce(function(t, f){
return t + f;
}, 0);
return total / this.messages.length;
};
Js8Thread.prototype.pushMessage = function(message) {
this.messages.push(message);
this.render();
};
Js8Thread.prototype.render = function() {
this.el.html(
'<td>' + this.renderTimestamp(this.getLatestTimestamp()) + '</td>' +
'<td class="decimal freq">' + Math.round(this.getAverageFrequency()) + '</td>' +
'<td class="message"><div>' + this.renderMessages() + '</div></td>'
);
};
Js8Thread.prototype.getLatestTimestamp = function() {
return this.messages[0].timestamp;
};
Js8Thread.prototype.isOpen = function() {
if (!this.messages.length) return true;
var last_message = this.messages[this.messages.length - 1];
return (last_message.thread_type & 2) === 0;
};
Js8Thread.prototype.renderMessages = function() {
var res = [];
for (var i = 0; i < this.messages.length; i++) {
var msg = this.messages[i];
if (msg.thread_type & 1) {
res.push('[ ');
} else if (i === 0 || msg.timestamp - this.messages[i - 1].timestamp > this.getMessageDuration()) {
res.push(' ... ');
}
res.push(msg.msg);
if (msg.thread_type & 2) {
res.push(' ]');
} else if (i === this.messages.length -1) {
res.push(' ... ');
}
}
return res.join('');
};
Js8Thread.prototype.getMessageDuration = function() {
switch (this.getMode()) {
case 'A':
return 15000;
case 'E':
return 30000;
case 'B':
return 10000;
case 'C':
return 6000;
}
};
Js8Thread.prototype.getMode = function() {
// we filter messages by mode, so the first one is as good as any
if (!this.messages.length) return;
return this.messages[0].mode;
};
Js8Thread.prototype.acceptsMode = function(mode) {
var currentMode = this.getMode();
return typeof(currentMode) === 'undefined' || currentMode === mode;
};
Js8Thread.prototype.renderTimestamp = function(timestamp) {
var t = new Date(timestamp);
var pad = function (i) {
return ('' + i).padStart(2, "0");
};
return pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds());
};
Js8Thread.prototype.purgeOldMessages = function() {
var now = new Date().getTime();
this.messages = this.messages.filter(function(m) {
// keep messages around for 20 minutes
return now - m.timestamp < 20 * 60 * 1000;
});
if (!this.messages.length) {
this.el.remove();
} else {
this.render();
}
return this.messages.length;
};
Js8Threader = function(el){
this.threads = [];
this.tbody = $(el).find('tbody');
var me = this;
this.interval = setInterval(function(){
me.purgeOldMessages();
}, 15000);
};
Js8Threader.prototype.purgeOldMessages = function() {
this.threads = this.threads.filter(function(t) {
return t.purgeOldMessages();
});
};
Js8Threader.prototype.findThread = function(freq, mode) {
var matching = this.threads.filter(function(thread) {
// max frequency deviation: 5 Hz. this may be a little tight.
return thread.isOpen() && thread.acceptsMode(mode) && Math.abs(thread.getAverageFrequency() - freq) <= 5;
});
matching.sort(function(a, b){
return b.getLatestTimestamp() - a.getLatestTimestamp();
});
return matching[0] || false;
};
Js8Threader.prototype.pushMessage = function(message) {
var thread;
// only look for exising threads if the message is not a starting message
if ((message.thread_type & 1) === 0) {
thread = this.findThread(message.freq, message.mode);
}
if (!thread) {
var line = $("<tr></tr>");
this.tbody.append(line);
thread = new Js8Thread(line);
this.threads.push(thread);
}
thread.pushMessage(message);
this.tbody.scrollTop(this.tbody[0].scrollHeight);
};
$.fn.js8 = function() {
if (!this.data('threader')) {
this.data('threader', new Js8Threader(this));
}
return this.data('threader');
};

View File

@ -1,70 +0,0 @@
function Measurement() {
this.reporters = [];
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();
this.reporters.forEach(function(r){ r.reset(); });
};
Measurement.prototype.report = function(range, interval, callback) {
var reporter = new Reporter(this, range, interval, callback);
this.reporters.push(reporter);
return reporter;
};
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);
};
Reporter.prototype.reset = function(){
this.samples = [];
};

View File

@ -1,55 +0,0 @@
var Modes = {
modes: [],
features: {},
panels: [],
setModes:function(json){
this.modes = json.map(function(m){ return new Mode(m); });
this.updatePanels();
$('#openwebrx-dialog-bookmark').bookmarkDialog().setModes(this.modes);
},
getModes:function(){
return this.modes;
},
setFeatures:function(features){
this.features = features;
this.updatePanels();
},
findByModulation:function(modulation){
matches = this.modes.filter(function(m) { return m.modulation === modulation; });
if (matches.length) return matches[0]
},
registerModePanel: function(el) {
this.panels.push(el);
},
initComplete: function() {
return this.modes.length && Object.keys(this.features).length;
},
updatePanels: function() {
this.panels.forEach(function(p) {
p.render();
p.startDemodulator();
});
}
};
var Mode = function(json){
this.modulation = json.modulation;
this.name = json.name;
this.type = json.type;
this.requirements = json.requirements;
this.squelch = json.squelch;
if (json.bandpass) {
this.bandpass = json.bandpass;
}
if (this.type === 'digimode') {
this.underlying = json.underlying;
}
};
Mode.prototype.isAvailable = function(){
return this.requirements.map(function(r){
return Modes.features[r];
}).reduce(function(a, b){
return a && b;
}, true);
};

View File

@ -1,165 +0,0 @@
ProgressBar = function(el) {
this.$el = $(el);
this.$innerText = $('<span class="openwebrx-progressbar-text">' + this.getDefaultText() + '</span>');
this.$innerBar = $('<div class="openwebrx-progressbar-bar"></div>');
this.$el.empty().append(this.$innerText, this.$innerBar);
this.$innerBar.css('width', '0%');
};
ProgressBar.prototype.getDefaultText = function() {
return '';
}
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) {
ProgressBar.call(this, el);
};
AudioBufferProgressBar.prototype = new ProgressBar();
AudioBufferProgressBar.prototype.getDefaultText = function() {
return 'Audio buffer [0 ms]';
};
AudioBufferProgressBar.prototype.setSampleRate = function(sampleRate) {
this.sampleRate = sampleRate;
};
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.getDefaultText = function() {
return 'Network usage [0 kbps]';
};
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.getDefaultText = function() {
return 'Audio stream [0 kbps]';
};
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);
};
AudioOutputProgressBar.prototype = new ProgressBar();
AudioOutputProgressBar.prototype.getDefaultText = function() {
return 'Audio output [0 sps]';
};
AudioOutputProgressBar.prototype.setSampleRate = function(sampleRate) {
this.maxRate = sampleRate * 1.25;
this.minRate = sampleRate * .25;
};
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.getDefaultText = function() {
return 'Clients [1]';
};
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.getDefaultText = function() {
return 'Server CPU [0%]';
};
CpuProgressBar.prototype.setUsage = function(usage) {
this.set(usage, "Server CPU [" + Math.round(usage * 100) + "%]", usage > .85);
};
ProgressBar.types = {
cpu: CpuProgressBar,
audiobuffer: AudioBufferProgressBar,
audiospeed: AudioSpeedProgressBar,
audiooutput: AudioOutputProgressBar,
clients: ClientsProgressBar,
networkspeed: NetworkSpeedProgressBar
}
$.fn.progressbar = function() {
if (!this.data('progressbar')) {
var constructor = ProgressBar.types[this.data('type')] || ProgressBar;
this.data('progressbar', new constructor(this));
}
return this.data('progressbar');
};

Some files were not shown because too many files have changed in this diff Show More