├── .gitignore ├── .gitmodules ├── .jshintrc ├── .travis.yml ├── AUTHORS ├── COPYING ├── MANIFEST.in ├── README.md ├── lint.sh ├── pylintrc ├── setup.py └── shinysdr ├── __init__.py ├── data └── dbs │ ├── Amateur bands, US.csv │ ├── Aviation.csv │ ├── Broadcast bands, US.csv │ ├── Broadcast bands, shortwave.csv │ ├── CB FRS GMRS, US.csv │ ├── ISM.csv │ ├── Other bands, US.csv │ └── Weather, US Canada Bermuda.csv ├── db_import ├── __init__.py └── tool.py ├── devices.py ├── filters.py ├── grc ├── __init__.py ├── demodulator.xml ├── modulator.xml └── multistage_channel_filter.xml ├── i ├── __init__.py ├── audiomux.py ├── blocks.py ├── config.py ├── db.py ├── dependencies.py ├── depgraph.py ├── ephemeris.py ├── json.py ├── math.py ├── modes.py ├── network │ ├── __init__.py │ ├── audio_http.py │ ├── base.py │ ├── export_http.py │ ├── export_ws.py │ ├── session_http.py │ ├── test_audio_http.py │ ├── test_base.py │ ├── test_export_http.py │ ├── test_export_ws.py │ ├── test_webapp.py │ └── webapp.py ├── persistence.py ├── poller.py ├── pycompat.py ├── receiver.py ├── roots.py ├── session.py ├── shared_test_objects.py ├── test_audiomux.py ├── test_blocks.py ├── test_config.py ├── test_db.py ├── test_dependencies.py ├── test_dependencies_cases │ ├── __init__.py │ ├── imports.py │ └── misc.py ├── test_depgraph.py ├── test_json.py ├── test_main.py ├── test_math.py ├── test_modes.py ├── test_modes_cases │ ├── __init__.py │ └── available_unavailable.py ├── test_persistence.py ├── test_poller.py ├── test_receiver.py ├── test_roots.py ├── test_session.py ├── test_top.py ├── top.py ├── webparts │ ├── block.template.xhtml │ ├── database-list.template.xhtml │ ├── error-page.template.xhtml │ └── index.template.xhtml └── webstatic │ ├── client │ ├── audio │ │ ├── analyser.js │ │ ├── bufferer.js │ │ ├── client-source.js │ │ ├── util.js │ │ └── ws-stream.js │ ├── client-configuration-module.js │ ├── coordination.js │ ├── database.js │ ├── domtools.js │ ├── events.js │ ├── gltools.js │ ├── icon │ │ ├── icon-32.png │ │ └── icon.svg │ ├── main.js │ ├── map │ │ ├── basemap.geojson.gz │ │ ├── curves-f.glsl │ │ ├── features-v.glsl │ │ ├── icons │ │ │ ├── default.svg │ │ │ ├── station-generic.svg │ │ │ └── station-user.svg │ │ ├── map-core.js │ │ ├── map-layers.js │ │ ├── points-f.glsl │ │ ├── sphere-f.glsl │ │ └── sphere-v.glsl │ ├── math.js │ ├── menu.svg │ ├── menus.js │ ├── network.js │ ├── pane-manager.js │ ├── themes │ │ ├── black.css │ │ └── gray.css │ ├── types.js │ ├── ui.css │ ├── values.js │ ├── widget.js │ ├── widgets.js │ └── widgets │ │ ├── appui.js │ │ ├── basic.js │ │ ├── dbui.js │ │ ├── scope-f.glsl │ │ ├── scope-pp1.glsl │ │ ├── scope-pp2.glsl │ │ ├── scope-v.glsl │ │ ├── scope.js │ │ ├── spectrum-common.glsl │ │ ├── spectrum-graph-f.glsl │ │ ├── spectrum-graph-v.glsl │ │ ├── spectrum-waterfall-f.glsl │ │ ├── spectrum-waterfall-v.glsl │ │ └── spectrum.js │ ├── index.html │ ├── manual │ ├── configuration.html │ ├── dbs.html │ ├── index.html │ ├── installation.html │ ├── manual-style.css │ ├── operation.html │ ├── programming.html │ └── troubleshooting.html │ ├── test │ ├── index.html │ ├── jasmine-glue.js │ ├── manual │ │ ├── spectrum-widgets-main.js │ │ └── spectrum-widgets.html │ ├── t │ │ ├── test_audio.js │ │ ├── test_coordination.js │ │ ├── test_database.js │ │ ├── test_domtools.js │ │ ├── test_events.js │ │ ├── test_map.js │ │ ├── test_math.js │ │ ├── test_network.js │ │ ├── test_pane-manager.js │ │ ├── test_types.js │ │ ├── test_values.js │ │ ├── test_widget.js │ │ └── test_widgets.js │ └── testutil.js │ └── tools │ ├── audio-scope-main.js │ ├── audio-scope.html │ ├── audio-spectrum-main.js │ ├── audio-spectrum.html │ ├── index.html │ ├── worklet-bug-main.js │ └── worklet-bug.html ├── interfaces.py ├── main.py ├── math.py ├── plugins ├── __init__.py ├── aprs │ ├── __init__.py │ └── client │ │ └── aprs.js ├── basic_demod.py ├── controller.py ├── dsd.py ├── elecraft │ ├── __init__.py │ └── client │ │ └── elecraft.js ├── ghpsdr.py ├── hamlib │ ├── __init__.py │ └── client │ │ └── hamlib.js ├── import_hfcc.py ├── import_satnogs.py ├── import_uls.py ├── limesdr.py ├── mode_s │ ├── __init__.py │ └── client │ │ ├── aircraft.svg │ │ └── mode_s.js ├── multimon.py ├── osmosdr.py ├── psk31 │ └── __init__.py ├── rebooter.py ├── rtl_433.py ├── rtty │ └── __init__.py ├── simulate.py ├── test_aprs.py ├── test_basic_demod.py ├── test_controller.py ├── test_dsd.py ├── test_elecraft.py ├── test_hamlib.py ├── test_mode_s.py ├── test_multimon.py ├── test_osmosdr.py ├── test_psk31.py ├── test_rtl_433.py ├── test_rtty.py ├── test_simulate.py └── wspr │ ├── __init__.py │ ├── blocks.py │ ├── client │ ├── w.svg │ └── wspr.js │ ├── demodulator.py │ ├── interfaces.py │ ├── telemetry.py │ ├── test_blocks.py │ ├── test_demodulator.py │ ├── test_telemetry.py │ └── wspr.csv ├── signals.py ├── telemetry.py ├── test_db_import.py ├── test_devices.py ├── test_filters.py ├── test_grc.py ├── test_manually ├── __init__.py ├── aprs_parser.py ├── channel_filter_benchmark.py └── channel_filter_testbed.grc ├── test_signals.py ├── test_telemetry.py ├── test_twisted_ext.py ├── test_types.py ├── test_values.py ├── testutil.py ├── twisted_ext.py ├── types.py ├── units.py └── values.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | *.pyc 3 | .eggs 4 | 5 | # Python setuptools 6 | /build 7 | /dist 8 | /*.egg-info 9 | 10 | # Twisted 11 | dropin.cache 12 | _trial_temp*/ 13 | 14 | # our files 15 | /shinysdr/deps/ 16 | 17 | # GRC-generated 18 | shinysdr/test/manual/channel_filter_testbed.py 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "shinysdr/deps/aprs-symbol-index"] 2 | path = shinysdr/deps/aprs-symbol-index 3 | url = https://github.com/hessu/aprs-symbol-index 4 | [submodule "shinysdr/deps/aprs-symbols"] 5 | path = shinysdr/deps/aprs-symbols 6 | url = https://github.com/hessu/aprs-symbols 7 | [submodule "shinysdr/deps/jasmine"] 8 | path = shinysdr/deps/jasmine 9 | url = https://github.com/jasmine/jasmine 10 | [submodule "shinysdr/deps/measviz"] 11 | path = shinysdr/deps/measviz 12 | url = https://github.com/kpreid/measviz/ 13 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Variable-related concerns 3 | "freeze": true, 4 | "futurehostile": true, 5 | "undef": true, 6 | "unused": "vars", 7 | "varstmt": true, 8 | 9 | // Language version concerns 10 | "strict": "global", 11 | "esversion": 6, 12 | 13 | // Misceallaneous enforcement 14 | "leanswitch": true, 15 | "noarg": true, 16 | 17 | // style options noted as "deprecated" (the corresponding warnings will be removed) 18 | "sub": true, 19 | "laxbreak": true, 20 | 21 | // Environment we are running in 22 | "browser": true, 23 | "devel": true, 24 | "predef": [ 25 | "AudioContext", 26 | "AudioWorkletNode", 27 | 28 | "define", 29 | "requirejs", 30 | 31 | "jasmineRequire", 32 | 33 | "measviz" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Need newer packages. 2 | dist: trusty 3 | language: python 4 | python: 5 | - "2.7" 6 | 7 | addons: 8 | apt: 9 | sources: 10 | # Need a newer gnuradio than Ubuntu Trusty has. 11 | # These PPAs were recommended by the gqrx project's installation advice. 12 | # http://gqrx.dk/download/install-ubuntu 13 | - sourceline: 'ppa:bladerf/bladerf' 14 | - sourceline: 'ppa:ettusresearch/uhd' 15 | - sourceline: 'ppa:myriadrf/drivers' 16 | - sourceline: 'ppa:myriadrf/gnuradio' 17 | packages: 18 | - gnuradio 19 | # - gr-air-modes # Available version is too old. 20 | - gr-osmosdr 21 | - libhamlib-utils 22 | 23 | before_install: 24 | - npm install jshint 25 | 26 | virtualenv: 27 | # Make gnuradio Python bindings visible to the virtualenv. 28 | system_site_packages: true 29 | 30 | install: 31 | - pip install ephem 32 | - pip install flake8 33 | # APT version is too old for us. 34 | - pip install pylint 35 | # APT version is too old for Twisted. 36 | - pip install --upgrade pyopenssl 37 | - pip install pyserial 38 | - pip install twisted 39 | - pip install txws 40 | 41 | script: 42 | # Also retrieves external dependencies 43 | - python setup.py fetch_deps 44 | # PATH in order to call jshint, which was installed non-globally above. 45 | - PATH="node_modules/.bin/:$PATH" ./lint.sh 46 | 47 | # PYTHONPATH as otherwise our own modules are not found. 48 | - PYTHONPATH=. trial shinysdr 49 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The ShinySDR project was started by Kevin Reid . 2 | 3 | ShinySDR currently includes contributions from the following authors 4 | and/or copyright holders (listed in alphabetical order). The copyright 5 | of specific code should be determined if needed by consulting version 6 | control history. 7 | 8 | Colin Dean 9 | hofschroeer 10 | Google LLC 11 | Phil Frost 12 | Quentin Smith 13 | Quentin Smith 14 | tdjsnelling 15 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING 2 | include README.md 3 | 4 | graft shinysdr/data 5 | graft shinysdr/plugins 6 | graft shinysdr/i/webparts 7 | graft shinysdr/i/webstatic 8 | 9 | # Tight includes to avoid packaging cruft. Don't forget to update! 10 | include shinysdr/deps/*.js 11 | include shinysdr/deps/aprs-symbol-index/generated/symbols.dense.json 12 | include shinysdr/deps/aprs-symbols/png/aprs-symbols-24-*.png 13 | include shinysdr/deps/jasmine/lib/jasmine-core/jasmine* 14 | graft shinysdr/deps/measviz/src 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ShinySDR 2 | ======== 3 | 4 | ShinySDR is the software component of a software-defined radio receiver. When combined with suitable hardware devices such as the RTL-SDR, HackRF, or USRP, it can be used to listen to or display data from a variety of radio transmissions. 5 | 6 | * **[More about ShinySDR](https://shinysdr.switchb.org/)** 7 | 8 | * **[Installing ShinySDR](https://shinysdr.switchb.org/manual/installation)** 9 | 10 | Copyright and License 11 | --------------------- 12 | 13 | Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Kevin Reid <kpreid@switchb.org> 14 | 15 | ShinySDR is free software: you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License as published by 17 | the Free Software Foundation, either version 3 of the License, or 18 | (at your option) any later version. 19 | 20 | ShinySDR is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | You should have received a copy of the GNU General Public License 26 | along with ShinySDR. If not, see . 27 | 28 | ### Additional information 29 | 30 | * The file `shinysdr/i/webstatic/client/map/basemap.geojson.gz` was derived from [the Natural Earth data set `ne_50m_admin_0_countries`, version 2.0.0](http://www.naturalearthdata.com/downloads/50m-cultural-vectors/). 31 | This data set [is in the public domain](http://www.naturalearthdata.com/about/terms-of-use/). 32 | * The APRS symbol graphics and descriptions used are from various sources and [collected by Heikki Hannikainen](https://github.com/hessu/aprs-symbols). 33 | See [author credits and licensing information](https://github.com/hessu/aprs-symbols/blob/master/COPYRIGHT.md). 34 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | # --- Parse options 6 | 7 | only_lang="${1:-all}" 8 | 9 | # --- Prepare to lint 10 | 11 | declare -a errors skips 12 | errors=() 13 | skips=() 14 | 15 | function linter { 16 | local lang="$1"; shift 17 | local program="$1"; shift 18 | if [[ "x$only_lang" == "x$lang" || "x$only_lang" == "xall" ]]; then 19 | echo "------ $0: Running $program ($lang)" 20 | "$program" "$@" || errors+=("$program") 21 | else 22 | skips+=("$program") 23 | fi 24 | } 25 | 26 | # --- Run linters 27 | 28 | # JS lint 29 | linter js jshint shinysdr/i/{webstatic,webparts} shinysdr/plugins 30 | 31 | # Python lint 32 | # pylint is last because it is the slowest linter. 33 | linter py flake8 --ignore=W191,W291,W293,W503,W504,E126,E128,E241,E501,E701 --exclude=deps shinysdr/ *.py 34 | linter py pylint --rcfile pylintrc shinysdr 35 | 36 | # --- Print summary and return status code 37 | 38 | if [[ ${#errors[*]} -ne 0 ]]; then 39 | echo "------ $0: Errors found by: ${errors[@]}" 40 | exit 1 41 | fi 42 | if [[ ${#skips[*]} -ne 0 ]]; then 43 | echo "------ $0: Skipped: ${skips[@]}" 44 | exit 2 45 | fi 46 | -------------------------------------------------------------------------------- /shinysdr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpreid/shinysdr/25022d36903ff67e036e82a22b6555a12a4d8e8a/shinysdr/__init__.py -------------------------------------------------------------------------------- /shinysdr/data/dbs/Amateur bands, US.csv: -------------------------------------------------------------------------------- 1 | Frequency,Mode,Name 2 | 1.800-2.000,LSB,"160-meter amateur" 3 | 3.500-3.600,CW,"80-meter amateur, data" 4 | 3.600-4.000,LSB,"80-meter (75-meter) amateur, phone" 5 | 5.3305,USB,60-meter amateur channel 1 6 | 5.3465,USB,60-meter amateur channel 2 7 | 5.3570,USB,60-meter amateur channel 3 8 | 5.3715,USB,60-meter amateur channel 4 9 | 5.4035,USB,60-meter amateur channel 5 10 | 7.000-7.125,CW,"40-meter amateur, data" 11 | 7.125-7.300,LSB,"40-meter amateur, phone" 12 | 10.100-10.150,CW,"30-meter amateur, data" 13 | 14.000-14.150,CW,"20-meter amateur, data" 14 | 14.150-14.350,USB,"20-meter amateur, phone" 15 | 18.068-18.110,CW,"17-meter amateur, data" 16 | 18.110-18.168,USB,"17-meter amateur, phone" 17 | 21.000-21.200,CW,"15-meter amateur, data" 18 | 21.200-21.450,USB,"15-meter amateur, phone" 19 | 24.890-24.930,CW,"12-meter amateur, data" 20 | 24.930-24.990,USB,"12-meter amateur, phone" 21 | 28.000-28.300,CW,"10-meter amateur, data" 22 | 28.300-29.700,USB,"10-meter amateur, phone" 23 | 50.0-54.0,,6-meter amateur 24 | 144.0-148.0,FM,2-meter amateur 25 | 219.0-225.0,FM,1.25-meter amateur 26 | 420.0-450.0,FM,70-cm amateur 27 | 902.0-928.0,,33-cm amateur 28 | 1240-1300,,23-cm amateur 29 | 2300-2310,,Amateur 30 | 2390-2450,,Amateur 31 | 3300-3500,,Amateur 32 | 5650-5925,,Amateur 33 | 10000-10500,,Amateur 34 | 24000-24250,,Amateur 35 | 47000-47200,,Amateur 36 | 76000-81000,,Amateur 37 | 122250-123000,,Amateur 38 | 134000-141000,,Amateur 39 | 241000-250000,,Amateur 40 | -------------------------------------------------------------------------------- /shinysdr/data/dbs/Aviation.csv: -------------------------------------------------------------------------------- 1 | Frequency,Mode,Name 2 | 0.190-0.435,AM,Aeronautical Radionavigation (Non-directional beacons) 3 | 0.510-0.530,AM,Aeronautical Radionavigation (Non-directional beacons) 4 | 2.850-3.155,AM,Aeronautical Mobile 5 | 3.400-3.500,AM,Aeronautical Mobile 6 | 4.65-4.75,,Aeronautical Radionavigation 7 | 5.45-5.73,AM,Aeronautical Mobile (US) 8 | 6.525-6.765,AM,Aeronautical Mobile (US) 9 | 8.815-9.040,AM,Aeronautical Mobile (US) 10 | 10.005-10.1,AM,Aeronautical Mobile (US) 11 | 11.175-11.4,AM,Aeronautical Mobile (US) 12 | 13.2-13.36,AM,Aeronautical Mobile (US) 13 | 15.010-15.10,AM,Aeronautical Mobile (US) 14 | 17.9-18.03,AM,Aeronautical Mobile (US) 15 | 21.924-22.0,AM,Aeronautical Mobile (US) 16 | 23.2-23.35,AM,Aeronautical Mobile (US) 17 | 74.8-75.2,AM,Aeronautical Radionavigation 18 | 108-117.975,AM,Aeronautical Radionavigation 19 | 117.975-137,AM,Aeronautical Mobile 20 | 121.5,AM,Guard (civilian aircraft emergency) 21 | 122.2,AM,Flight Service (US) 22 | 122.75,AM,Air-to-air (US) 23 | 243,AM,Guard (military aircraft emergency) 24 | 328.6-335.4,AM,Aeronautical Radionavigation 25 | 849-851,AM,Aeronautical Mobile (US) 26 | 894-896,AM,Aeronautical Mobile (US) 27 | 960-1215,AM,Aeronautical Radionavigation 28 | 978.0,MODE-S,UAT transceivers 29 | 1030.0,ignore,aircraft ATCRBS interrogators 30 | 1090.0,MODE-S,aircraft ATCRBS transponders 31 | 1300-1350,,Aeronautical Radionavigation 32 | 1559-1626.5,,Aeronautical Radionavigation 33 | 2700-2900,,Aeronautical Radionavigation 34 | 3500-3650,,Aeronautical Radionavigation (Ground) 35 | -------------------------------------------------------------------------------- /shinysdr/data/dbs/Broadcast bands, US.csv: -------------------------------------------------------------------------------- 1 | Mode,Frequency,Name,Comment,Latitude,Longitude 2 | ,0.060,WWVB,,40.677722,-105.038819 3 | AM,0.530,Traveler's Information,,, 4 | AM,0.530-1.705,AM broadcast,,, 5 | AM,1.610,Traveler's Information,,, 6 | AM,2.5,WWV 2.5,,40.682,-105.041944 7 | AM,5.0,WWV 5,,40.678361,-105.04025 8 | AM,10.0,WWV 10,,40.679944,-105.040306 9 | AM,15.0,WWV 15,,40.679167,-105.040139 10 | AM,20.0,WWV 20,,40.681417,-105.04125 11 | AM,2.5,WWVH 2.5,,21.989139,-159.764556 12 | AM,5.0,WWVH 5,,21.986333,-159.762444 13 | AM,10.0,WWVH 10,,21.988389,-159.76425 14 | AM,15.0,WWVH 15,,21.987583,-159.763889 15 | ATSC,54-72,ATSC TV channels 2-4,,, 16 | ATSC,76-88,ATSC TV channels 5-6,,, 17 | ATSC,174-216,ATSC TV channels 7-13,,, 18 | ATSC,470-608,ATSC TV channels 14-36,,, 19 | ATSC,614-692,ATSC TV channels 38-50,,, 20 | WFM,88-108,FM broadcast,,, 21 | -------------------------------------------------------------------------------- /shinysdr/data/dbs/Broadcast bands, shortwave.csv: -------------------------------------------------------------------------------- 1 | Frequency,Mode,Name 2 | 2.300-2.495,AM,Shortwave broadcast (local) 3 | 3.200-3.400,AM,Shortwave broadcast (local) 4 | 3.900-4.000,AM,Shortwave broadcast 5 | 4.750-5.060,AM,Shortwave broadcast (night) 6 | 5.900-6.200,AM,Shortwave broadcast (night) 7 | 7.200-7.450,AM,Shortwave broadcast (night) 8 | 9.400-9.900,AM,Shortwave broadcast (night) 9 | 11.500-12.100,AM,Shortwave broadcast 10 | 13.570-13.870,AM,Shortwave broadcast 11 | 15.100-15.800,AM,Shortwave broadcast (day) 12 | 17.480-17.900,AM,Shortwave broadcast (day) 13 | 18.900-19.020,AM,Shortwave broadcast 14 | 21.450-21.850,AM,Shortwave broadcast (variable) 15 | 25.600-26.100,AM,Shortwave broadcast (variable) 16 | -------------------------------------------------------------------------------- /shinysdr/data/dbs/CB FRS GMRS, US.csv: -------------------------------------------------------------------------------- 1 | Mode,Name,Frequency 2 | AM,CB ch. 1,26.965 3 | AM,CB ch. 2,26.975 4 | AM,CB ch. 3,26.985 5 | AM,CB ch. 4,27.005 6 | AM,CB ch. 5,27.015 7 | AM,CB ch. 6,27.025 8 | AM,CB ch. 7,27.035 9 | AM,CB ch. 8,27.055 10 | AM,CB ch. 9,27.065 11 | AM,CB ch. 10,27.075 12 | AM,CB ch. 11,27.085 13 | AM,CB ch. 12,27.105 14 | AM,CB ch. 13,27.115 15 | AM,CB ch. 14,27.125 16 | AM,CB ch. 15,27.135 17 | AM,CB ch. 16,27.155 18 | AM,CB ch. 17,27.165 19 | AM,CB ch. 18,27.175 20 | AM,CB ch. 19,27.185 21 | AM,CB ch. 20,27.205 22 | AM,CB ch. 21,27.215 23 | AM,CB ch. 22,27.225 24 | AM,CB ch. 23,27.255 25 | AM,CB ch. 24,27.235 26 | AM,CB ch. 25,27.245 27 | AM,CB ch. 26,27.265 28 | AM,CB ch. 27,27.275 29 | AM,CB ch. 28,27.285 30 | AM,CB ch. 29,27.295 31 | LSB,CB ch. 30,27.305 32 | LSB,CB ch. 31,27.315 33 | LSB,CB ch. 32,27.325 34 | LSB,CB ch. 33,27.335 35 | LSB,CB ch. 34,27.345 36 | LSB,CB ch. 35,27.355 37 | LSB,CB ch. 36,27.365 38 | LSB,CB ch. 37,27.375 39 | LSB,CB ch. 38,27.385 40 | LSB,CB ch. 39,27.395 41 | LSB,CB ch. 40,27.405 42 | FM,GMRS out/simplex,462.550 43 | FM,GMRS out/simplex,462.575 44 | FM,GMRS out/simplex,462.600 45 | FM,GMRS out/simplex,462.625 46 | FM,GMRS out/simplex,462.650 47 | FM,GMRS out/simplex,462.675 48 | FM,GMRS out/simplex,462.700 49 | FM,GMRS out/simplex,462.725 50 | FM,FRS ch. 1/GMRS,462.5625 51 | FM,FRS ch. 2/GMRS,462.5875 52 | FM,FRS ch. 3/GMRS,462.6125 53 | FM,FRS ch. 4/GMRS,462.6375 54 | FM,FRS ch. 5/GMRS,462.6625 55 | FM,FRS ch. 6/GMRS,462.6875 56 | FM,FRS ch. 7/GMRS,462.7125 57 | FM,FRS ch. 8,467.5625 58 | FM,FRS ch. 9,467.5875 59 | FM,FRS ch. 10,467.6125 60 | FM,FRS ch. 11,467.6375 61 | FM,FRS ch. 12,467.6625 62 | FM,FRS ch. 13,467.6875 63 | FM,FRS ch. 14,467.7125 64 | -------------------------------------------------------------------------------- /shinysdr/data/dbs/ISM.csv: -------------------------------------------------------------------------------- 1 | Frequency,Name 2 | 6.765-6.795,ISM: Subject to local acceptance 3 | 13.553-13.567,ISM: Worldwide 4 | 26.957-27.283,ISM: Worldwide 5 | 40.660-40.700,ISM: Worldwide 6 | 433.050-434.790,ISM: Region 1 only and subject to local acceptance 7 | 902.000-928.000,ISM: Region 2 only (with some exceptions) 8 | 2400-2500,ISM: Worldwide 9 | 5725-5875,ISM: Worldwide 10 | 24000-24250,ISM: Worldwide 11 | 61000-61500,ISM: Subject to local acceptance 12 | 122000-123000,ISM: Subject to local acceptance 13 | 244000-246000,ISM: Subject to local acceptance 14 | -------------------------------------------------------------------------------- /shinysdr/data/dbs/Weather, US Canada Bermuda.csv: -------------------------------------------------------------------------------- 1 | Frequency,Mode,Name 2 | 162.400,FM,Weather WX2 / Public Alert 1 3 | 162.425,FM,Weather WX4 / Public Alert 2 4 | 162.450,FM,Weather WX5 / Public Alert 3 5 | 162.475,FM,Weather WX3 / Public Alert 4 6 | 162.500,FM,Weather WX6 / Public Alert 5 7 | 162.525,FM,Weather WX7 / Public Alert 6 8 | 162.550,FM,Weather WX1 / Public Alert 7 9 | 162.650,FM,Weather WX8 10 | 161.775,FM,Weather WX9 11 | 163.275,FM,Weather WX10 12 | -------------------------------------------------------------------------------- /shinysdr/db_import/tool.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | import argparse 21 | import sys 22 | 23 | import six 24 | 25 | from twisted.plugin import getPlugins 26 | 27 | from shinysdr.i.db import normalize_record, write_csv_file 28 | from shinysdr.db_import import GeoFilter, IImporter, _IImporterDef 29 | from shinysdr import plugins 30 | 31 | 32 | def _general_warning_callback(msg): 33 | print(msg, file=sys.stderr) 34 | 35 | 36 | def _add_file_wrapper(importer, filename, open_file): 37 | def warning_callback(msg): 38 | print(u'%s:%s' % (filename, msg), file=sys.stderr) 39 | 40 | importer.add_file(filename, open_file, warning_callback) 41 | 42 | 43 | _IMPORTER_DEFS = {p.name: p for p in getPlugins(_IImporterDef, plugins) if p.available} 44 | 45 | 46 | def _importer_list_msg(): 47 | out = 'Known importers:\n' 48 | for name, idef in six.iteritems(_IMPORTER_DEFS): 49 | out += ' %s: %s\n' % (name, idef.description) 50 | return out 51 | 52 | 53 | def _parse_args(argv): 54 | parser = argparse.ArgumentParser( 55 | prog=argv[0], 56 | epilog=_importer_list_msg(), 57 | formatter_class=argparse.RawDescriptionHelpFormatter) 58 | parser.add_argument('importer_name', metavar='IMPORTER', 59 | help='importer to use') 60 | parser.add_argument('filenames', metavar='FILE', nargs='*', 61 | help='files to import (standard input if omitted)') 62 | parser.add_argument('--near', metavar='LAT,LON,RADIUS', 63 | help='include only records within RADIUS (in meters) of LAT,LON') 64 | return parser.parse_args(args=argv[1:]) 65 | 66 | 67 | def import_main(argv=None, out=None): 68 | # NOTE: This function is referenced from setup.py entry_points. 69 | """Entry point for the offline import command. 70 | 71 | Optional arguments are for testing. 72 | """ 73 | options = _parse_args(argv if argv is not None else sys.argv) 74 | importer_name = options.importer_name 75 | filenames = options.filenames 76 | 77 | if importer_name not in _IMPORTER_DEFS: 78 | print('Unknown importer: %r.\n%s' % (importer_name, _importer_list_msg()), file=sys.stderr) 79 | sys.exit(1) 80 | importer = IImporter(_IMPORTER_DEFS[importer_name].importer_class()) 81 | 82 | if options.near: 83 | geo_filter_parts = [int(s.strip()) for s in options.near.split(',')] 84 | importer = GeoFilter( 85 | importer=importer, 86 | latitude=geo_filter_parts[0], 87 | longitude=geo_filter_parts[2], 88 | radius=geo_filter_parts[2]) 89 | 90 | if filenames: 91 | for filename in filenames: 92 | with open(filename, 'r') as open_file: 93 | _add_file_wrapper(importer, filename, open_file) 94 | else: 95 | _add_file_wrapper(importer, "-", sys.stdin) 96 | 97 | records = [] 98 | 99 | def add_record(record): 100 | records.append(normalize_record(record)) 101 | 102 | importer.create_database( 103 | add_record, 104 | warning_callback=_general_warning_callback) 105 | write_csv_file(out or sys.stdout, dict(enumerate(records))) 106 | 107 | 108 | if __name__ == '__main__': 109 | import_main() 110 | -------------------------------------------------------------------------------- /shinysdr/grc/demodulator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ShinySDR Demodulator 4 | shinysdr_demodulator 5 | [ShinySDR] 6 | import shinysdr.grc 7 | shinysdr.grc.DemodulatorAdapter(mode=$mode, input_rate=$input_rate, output_rate=$output_rate) 8 | 9 | Mode 10 | mode 11 | 'AM' 12 | string 13 | 14 | 15 | Input Sample Rate 16 | input_rate 17 | samp_rate 18 | real 19 | 20 | 21 | Output Sample Rate 22 | output_rate 23 | samp_rate 24 | real 25 | 26 | 27 | in 28 | complex 29 | 30 | 31 | left 32 | float 33 | 34 | 35 | right 36 | float 37 | 38 | -------------------------------------------------------------------------------- /shinysdr/grc/modulator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ShinySDR Modulator 4 | shinysdr_modulator 5 | [ShinySDR] 6 | import shinysdr.grc 7 | shinysdr.grc.ModulatorAdapter(mode=$mode, input_rate=$input_rate, output_rate=$output_rate) 8 | 9 | Mode 10 | mode 11 | 'AM' 12 | string 13 | 14 | 15 | Input Sample Rate 16 | input_rate 17 | samp_rate 18 | real 19 | 20 | 21 | Output Sample Rate 22 | output_rate 23 | samp_rate 24 | real 25 | 26 | 27 | in 28 | float 29 | 30 | 31 | out 32 | complex 33 | 34 | -------------------------------------------------------------------------------- /shinysdr/grc/multistage_channel_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Multistage Channel Filter 4 | shinysdr_MultistageChannelFilter 5 | [ShinySDR] 6 | import shinysdr.filters 7 | shinysdr.filters.MultistageChannelFilter(input_rate=$input_rate, output_rate=$output_rate, cutoff_freq=$cutoff_freq, transition_width=$transition_width, center_freq=$center_freq) 8 | set_cutoff_freq($cutoff_freq) 9 | set_transition_width($transition_width) 10 | set_center_freq($center_freq) 11 | 12 | Input Sample Rate 13 | input_rate 14 | samp_rate 15 | real 16 | 17 | 18 | Output Sample Rate 19 | output_rate 20 | samp_rate 21 | real 22 | 23 | 24 | Cutoff Frequency 25 | cutoff_freq 26 | 10000 27 | real 28 | 29 | 30 | Transition Band Width 31 | transition_width 32 | 1000 33 | real 34 | 35 | 36 | Center Frequency 37 | center_freq 38 | 0 39 | real 40 | 41 | 42 | in 43 | complex 44 | 45 | 46 | out 47 | complex 48 | 49 | -------------------------------------------------------------------------------- /shinysdr/i/__init__.py: -------------------------------------------------------------------------------- 1 | """Package for ShinySDR implementation modules. 2 | 3 | Everything under this package is explicitly not a stable interface -- that is, not intended for use by plugins or any other kind of third-party code. It is organized according to convenience rather than API design. 4 | 5 | (On the other hand, there aren't guarantees of stability outside this package. It's just particularly not guaranteed here.) 6 | """ 7 | -------------------------------------------------------------------------------- /shinysdr/i/ephemeris.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | import json 21 | import math 22 | import time 23 | 24 | from twisted.web.resource import Resource 25 | 26 | import ephem 27 | 28 | __all__ = [] # appended later 29 | 30 | 31 | _RADIANS_TO_DEGREES = 180 / math.pi 32 | t0 = time.time() 33 | 34 | 35 | class EphemerisResource(Resource): 36 | isLeaf = True 37 | 38 | def __init__(self): 39 | Resource.__init__(self) 40 | 41 | def render_GET(self, request): 42 | # pylint: disable=no-member 43 | 44 | # This will eventually take satellite parameters and return current position/velocity. For now, it does the sun. 45 | o = ephem.Observer() 46 | o.date = ephem.now() 47 | o.lat = '0' 48 | o.lon = '0' 49 | sun = ephem.Sun() 50 | sun.compute(o) 51 | # az and alt are now relative to an observer on the surface at 0N 0E, which is a silly coordinate system but the best I could get reliably. 52 | x = math.sin(sun.az) * math.cos(sun.alt) 53 | y = math.cos(sun.az) * math.cos(sun.alt) 54 | z = -math.sin(sun.alt) 55 | 56 | request.setHeader(b'Content-Type', b'application/json') 57 | return json.dumps([x, y, z]) 58 | 59 | 60 | __all__.append('EphemerisResource') 61 | -------------------------------------------------------------------------------- /shinysdr/i/json.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013, 2014, 2015, 2016, 2018 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | """Customized JSON serialization for persistence and networking.""" 19 | 20 | from __future__ import absolute_import, division, print_function, unicode_literals 21 | 22 | import json 23 | 24 | import six 25 | 26 | from zope.interface import Interface 27 | 28 | 29 | # not itself a type in the sense meant here, but the least-wrong-so-far place to put this as it is used by types 30 | # This should be referenced from external code as shinysdr.types.IJsonSerializable. 31 | class IJsonSerializable(Interface): 32 | """Value objects which can be serialized as JSON structures. 33 | 34 | Only value objects, not things like ExportedState, should implement this interface. 35 | """ 36 | def to_json(self): 37 | """Return a JSON representation of this object. 38 | 39 | The representation should be a JSON object (dict) which has a key u'type' whose value is a string uniquely identifying the class (loosely speaking) being represented. No well-defined namespace organization has yet been established for these type strings. 40 | """ 41 | 42 | 43 | # JSONEncoder configured for ShinySDR API use. 44 | # Do not use this directly; use serialize() instead. 45 | _json_encoder_for_serial = json.JSONEncoder( 46 | ensure_ascii=False, 47 | check_circular=False, 48 | allow_nan=True, 49 | sort_keys=True, 50 | separators=(',', ':')) 51 | 52 | 53 | def serialize(obj): 54 | """JSON-encode values for clients, both HTTP and state stream WebSocket.""" 55 | structure = transform_for_json(obj) 56 | # Python 2's JSONEncoder is not 100% consistent about which type of string it returns when ensure_ascii is false 57 | return six.text_type(_json_encoder_for_serial.encode(structure)) 58 | 59 | 60 | def transform_for_json(obj): 61 | """Replaces serializable objects in a data structure with JSON-compatible representations. 62 | 63 | Use serialize() to produce a JSON string instead of this, unless this is what you need.""" 64 | # Cannot implement this using the default hook in JSONEncoder because we want to override the behavior for namedtuples (normally treated as tuples), which cannot be done otherwise. 65 | if IJsonSerializable.providedBy(obj): 66 | return transform_for_json(obj.to_json()) 67 | elif isinstance(obj, tuple) and hasattr(obj, '_asdict'): # namedtuple 68 | # TODO: Consider replreplacing all uses of this generic namedtuple handling with IJsonSerializable now that we have that. 69 | return {k: transform_for_json(v) for k, v in six.iteritems(obj._asdict())} 70 | elif isinstance(obj, dict): 71 | return {k: transform_for_json(v) for k, v in six.iteritems(obj)} 72 | elif isinstance(obj, (list, tuple)): 73 | return [transform_for_json(v) for v in obj] 74 | else: 75 | return obj 76 | -------------------------------------------------------------------------------- /shinysdr/i/modes.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013, 2014, 2015, 2016, 2018 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | # TODO write module documentation, or revisit whether this module needs to exist 19 | 20 | from __future__ import absolute_import, division, print_function, unicode_literals 21 | 22 | from twisted.plugin import getPlugins 23 | from twisted.logger import Logger 24 | from zope.interface import Interface 25 | 26 | from shinysdr import plugins 27 | 28 | 29 | __all__ = [] # appended later 30 | 31 | _log = Logger() 32 | 33 | 34 | class IModeDef(Interface): 35 | """ 36 | Demodulator plugin description interface. 37 | 38 | See shinysdr.interfaces.ModeDef for the actual type. 39 | """ 40 | # Only needed to make the plugin system work 41 | # TODO write interface methods anyway 42 | 43 | 44 | # TODO: Refactor _ModeTable so that it can be tested (does not hardcode getPlugins) 45 | 46 | 47 | # Object for memoizing results of getPlugins(IModeDef) 48 | class _ModeTable(object): 49 | def __init__(self, plugin_package): 50 | self.__all_modes = {} 51 | self.__available_modes = {} 52 | _log.info('Loading mode plugins...') 53 | for mode_def in getPlugins(IModeDef, plugin_package): 54 | if mode_def.mode in self.__all_modes: 55 | raise Exception('Mode name conflict for mode {!r}'.format(mode_def.mode)) 56 | self.__all_modes[mode_def.mode] = mode_def 57 | if mode_def.available: 58 | _log.debug(' Mode: {0.mode}'.format(mode_def)) 59 | self.__available_modes[mode_def.mode] = mode_def 60 | else: 61 | _log.info('Mode {0.mode} unavailable\n{0.unavailability}'.format(mode_def)) 62 | _log.info('...done mode plugins.') 63 | 64 | def get_modes(self, include_unavailable): 65 | if include_unavailable: 66 | return list(self.__all_modes.values()) 67 | else: 68 | return list(self.__available_modes.values()) 69 | 70 | def lookup_mode(self, mode, include_unavailable): 71 | if include_unavailable: 72 | return self.__all_modes.get(mode) 73 | else: 74 | return self.__available_modes.get(mode) 75 | 76 | 77 | # pylint: disable=global-statement 78 | # This is memoizing what is global anyway and mostly-immutable. namely getPlugins() results (which ultimately depend on module imports). 79 | _mode_table = None 80 | 81 | 82 | def _get_mode_table(): 83 | global _mode_table 84 | if _mode_table is None: 85 | _mode_table = _ModeTable(plugins) 86 | return _mode_table 87 | 88 | 89 | def get_modes(include_unavailable=False): 90 | return _get_mode_table().get_modes(include_unavailable=include_unavailable) 91 | 92 | 93 | __all__.append('get_modes') 94 | 95 | 96 | def lookup_mode(mode, include_unavailable=False): 97 | return _get_mode_table().lookup_mode(mode, include_unavailable=include_unavailable) 98 | 99 | 100 | __all__.append('lookup_mode') 101 | -------------------------------------------------------------------------------- /shinysdr/i/network/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpreid/shinysdr/25022d36903ff67e036e82a22b6555a12a4d8e8a/shinysdr/i/network/__init__.py -------------------------------------------------------------------------------- /shinysdr/i/network/session_http.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, 2015, 2016, 2017, 2018 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | """Session-specific HTTP-specific code. 19 | 20 | TODO: Not sure whether this module makes sense. 21 | """ 22 | 23 | from __future__ import absolute_import, division, print_function, unicode_literals 24 | 25 | from twisted.web import template 26 | 27 | import shinysdr.i.db 28 | from shinysdr.i.ephemeris import EphemerisResource 29 | from shinysdr.i.json import serialize 30 | from shinysdr.i.network.base import AUDIO_STREAM_PATH_ELEMENT, CAP_OBJECT_PATH_ELEMENT, ElementRenderingResource, EntryPointIndexElement, SlashedResource, prepath_escaped, template_filepath 31 | from shinysdr.i.network.export_http import BlockResource, FlowgraphVizResource 32 | from shinysdr.i.network.audio_http import AudioStreamResource 33 | 34 | 35 | class SessionResource(SlashedResource): 36 | # TODO ask the session for the dbs 37 | def __init__(self, session, wcommon, read_only_dbs, writable_db): 38 | SlashedResource.__init__(self) 39 | 40 | # UI entry point 41 | self.putChild('', ElementRenderingResource(_RadioIndexHtmlElement(wcommon))) 42 | 43 | # Exported radio control objects 44 | self.putChild(CAP_OBJECT_PATH_ELEMENT, BlockResource(session, wcommon, _not_deletable)) 45 | 46 | # Frequency DB 47 | self.putChild('dbs', shinysdr.i.db.DatabasesResource(read_only_dbs)) 48 | self.putChild('wdb', shinysdr.i.db.DatabaseResource(writable_db)) 49 | 50 | # Debug graph 51 | self.putChild('flow-graph', FlowgraphVizResource(wcommon.reactor, session.flowgraph_for_debug())) 52 | 53 | # Ephemeris 54 | self.putChild('ephemeris', EphemerisResource()) 55 | 56 | # Standard audio-file-over-HTTP audio stream (the ShinySDR web client uses WebSockets instead, but both have the same path modulo protocol) 57 | self.putChild(AUDIO_STREAM_PATH_ELEMENT, AudioStreamResource(session)) 58 | 59 | 60 | class _RadioIndexHtmlElement(EntryPointIndexElement): 61 | loader = template.XMLFile(template_filepath.child('index.template.xhtml')) 62 | 63 | @template.renderer 64 | def title(self, request, tag): 65 | return tag(self.entry_point_wcommon.title) 66 | 67 | @template.renderer 68 | def quoted_audio_url(self, request, tag): 69 | return tag(serialize(self.entry_point_wcommon.make_websocket_url(request, 70 | prepath_escaped(request) + AUDIO_STREAM_PATH_ELEMENT))) 71 | 72 | 73 | def _not_deletable(): 74 | # TODO audit uses of this function 75 | # TODO plumb up a user-friendly (proper HTTP code) error 76 | raise Exception('Attempt to delete session root') 77 | -------------------------------------------------------------------------------- /shinysdr/i/network/test_base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.trial import unittest 21 | from twisted.internet import reactor as the_reactor 22 | 23 | from shinysdr.i.network.base import WebServiceCommon 24 | 25 | 26 | class TestWebServiceCommon(unittest.TestCase): 27 | def test_make_websocket_url_relative(self): 28 | wcommon = WebServiceCommon.stub(the_reactor) 29 | request = FakeRequest() 30 | self.assertRaises(AssertionError, lambda: wcommon.make_websocket_url(request, 'boguspath')) 31 | 32 | def test_make_websocket_url_without_base_url(self): 33 | wcommon = WebServiceCommon( 34 | reactor=the_reactor, 35 | title='', 36 | ws_endpoint_string='tcp:1234') 37 | request = FakeRequest() 38 | self.assertEqual( 39 | 'ws://fake-request-hostname:1234/testpath', 40 | wcommon.make_websocket_url(request, '/testpath')) 41 | 42 | def test_make_websocket_url_with_base_url(self): 43 | wcommon = WebServiceCommon( 44 | reactor=the_reactor, 45 | title='', 46 | ws_endpoint_string='tcp:1234', 47 | ws_base_url='wss://wshost:5678/') 48 | request = FakeRequest() 49 | self.assertEqual( 50 | 'wss://wshost:5678/testpath', 51 | wcommon.make_websocket_url(request, '/testpath')) 52 | 53 | def test_make_websocket_url_with_base_url_with_path(self): 54 | wcommon = WebServiceCommon( 55 | reactor=the_reactor, 56 | title='', 57 | ws_endpoint_string='tcp:1234', 58 | ws_base_url='wss://wshost:5678/ws/') 59 | request = FakeRequest() 60 | self.assertEqual( 61 | 'wss://wshost:5678/ws/testpath', 62 | wcommon.make_websocket_url(request, '/testpath')) 63 | 64 | 65 | class FakeRequest(object): 66 | """Pretends to be a twisted.web.http.Request. Isn't because that would need more setup.""" 67 | def getRequestHostname(self): 68 | return 'fake-request-hostname' 69 | 70 | 71 | # TODO: test render_error_page 72 | # TODO: test endpoint_string_to_url 73 | # TODO: test prepath_escaped 74 | # TODO: test parse_audio_stream_options 75 | -------------------------------------------------------------------------------- /shinysdr/i/network/test_export_http.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017 Kevin Reid and the ShinySDR contributors 3 | # 4 | # This file is part of ShinySDR. 5 | # 6 | # ShinySDR is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # ShinySDR is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with ShinySDR. If not, see . 18 | 19 | from __future__ import absolute_import, division, print_function, unicode_literals 20 | 21 | import json 22 | 23 | from twisted.internet import defer 24 | from twisted.internet import reactor as the_reactor 25 | from twisted.trial import unittest 26 | 27 | from shinysdr.i.network.base import SiteWithDefaultHeaders, WebServiceCommon 28 | from shinysdr.i.network.export_http import BlockResource 29 | from shinysdr.testutil import assert_http_resource_properties, http_get, http_request 30 | from shinysdr.values import ExportedState, exported_value, setter 31 | 32 | 33 | class TestBlockTreeResources(unittest.TestCase): 34 | # TODO: Have less boilerplate "set up a web server". 35 | 36 | def setUp(self): 37 | wcommon = WebServiceCommon.stub(reactor=the_reactor) 38 | self.obj = StateSpecimen() 39 | r = BlockResource(self.obj, wcommon, None) 40 | self.port = the_reactor.listenTCP(0, SiteWithDefaultHeaders(r), interface="127.0.0.1") # pylint: disable=no-member 41 | 42 | def tearDown(self): 43 | return self.port.stopListening() 44 | 45 | def __url(self, path): 46 | return 'http://127.0.0.1:%i%s' % (self.port.getHost().port, path) 47 | 48 | def test_leaf_cell_common(self): 49 | return assert_http_resource_properties(self, self.__url('/leaf_cell')) 50 | 51 | @defer.inlineCallbacks 52 | def test_leaf_cell_get(self): 53 | response, data = yield http_get(the_reactor, self.__url('/leaf_cell')) 54 | self.assertEqual(response.headers.getRawHeaders('Content-Type'), ['application/json']) 55 | self.assertEqual([1, 2, 3], json.loads(data)) 56 | 57 | @defer.inlineCallbacks 58 | def test_leaf_cell_put(self): 59 | yield http_request(the_reactor, self.__url('/leaf_cell'), 60 | method='PUT', 61 | body='[3, 4, 5]') 62 | 63 | response, data = yield http_get(the_reactor, self.__url('/leaf_cell')) 64 | self.assertEqual(response.headers.getRawHeaders('Content-Type'), ['application/json']) 65 | self.assertEqual([3, 4, 5], json.loads(data)) 66 | 67 | # TODO: test BlockResource behavior rather than just the leaf 68 | 69 | 70 | class StateSpecimen(ExportedState): 71 | """Helper for TestBlockTreeResources""" 72 | 73 | def __init__(self): 74 | self.value = [1, 2, 3] 75 | 76 | @exported_value(type=list, changes='this_setter') 77 | def get_leaf_cell(self): 78 | return self.value 79 | 80 | @setter 81 | def set_leaf_cell(self, value): 82 | self.value = value 83 | -------------------------------------------------------------------------------- /shinysdr/i/pycompat.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | import re 21 | 22 | import six 23 | 24 | 25 | def bytes_or_ascii(value): 26 | """Emulate Python 2 behavior of automatic string encoding or pass-through.""" 27 | if isinstance(value, bytes): 28 | return value 29 | else: 30 | return six.text_type(value).encode('ascii') 31 | 32 | 33 | def defaultstr(value): 34 | """Equivalent to str(); used to explicitly indicate situations where what we need is the default string type for the Python version. 35 | 36 | The common such situations are: 37 | * GNU Radio block names 38 | * the built-in array module, which in Python 2.7.6 does not accept a unicode string. 39 | """ 40 | return str(value) 41 | 42 | 43 | def repr_no_string_tag(value): 44 | """As repr() but if the result would start with "b'" or "u'", remove the "b" or "u". 45 | 46 | This allows for consistent output between Python 2 and 3 but also may be used for user-facing strings where on Python 2 we don't particularly want to show a "u". 47 | """ 48 | return re.sub("^[bu]'", "'", repr(value)) 49 | -------------------------------------------------------------------------------- /shinysdr/i/shared_test_objects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 Kevin Reid and the ShinySDR contributors 3 | # 4 | # This file is part of ShinySDR. 5 | # 6 | # ShinySDR is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # ShinySDR is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with ShinySDR. If not, see . 18 | 19 | """Test objects used by client-server integration and equivalence tests.""" 20 | 21 | from __future__ import absolute_import, division, print_function, unicode_literals 22 | 23 | from shinysdr.values import ExportedState, exported_value 24 | 25 | 26 | SHARED_TEST_OBJECTS_CAP = 'shared_test_objects' 27 | 28 | 29 | class SharedTestObjects(ExportedState): 30 | @exported_value(type=unicode, changes='never') 31 | def get_smoke_test(self): 32 | return 'SharedTestObjects exists' 33 | -------------------------------------------------------------------------------- /shinysdr/i/test_audiomux.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.trial import unittest 21 | 22 | from gnuradio import blocks 23 | from gnuradio import gr 24 | 25 | from shinysdr.i.audiomux import AudioManager 26 | 27 | 28 | class TestAudioManager(unittest.TestCase): 29 | def setUp(self): 30 | self.tb = gr.top_block() 31 | self.p = AudioManager( 32 | graph=self.tb, 33 | audio_config=None, 34 | stereo=False) 35 | 36 | def test_smoke(self): 37 | rs = self.p.reconnecting() 38 | rs.input(ConnectionCanarySource(self.tb), 10000, 'client') 39 | rs.finish_bus_connections() 40 | self.tb.start() 41 | self.tb.stop() 42 | self.tb.wait() 43 | 44 | def test_wrong_dest_name(self): 45 | """ 46 | Shouldn't fail to construct a valid flow graph, despite the bad name. 47 | """ 48 | rs = self.p.reconnecting() 49 | rs.input(ConnectionCanarySource(self.tb), 10000, 'bogusname') 50 | rs.finish_bus_connections() 51 | self.tb.start() 52 | self.tb.stop() 53 | self.tb.wait() 54 | 55 | 56 | def ConnectionCanarySource(graph): 57 | """ 58 | Set up a partial graph to detect its output not being connected 59 | """ 60 | source = blocks.vector_source_f([]) 61 | copy = blocks.copy(gr.sizeof_float) 62 | graph.connect(source, copy) 63 | return copy 64 | -------------------------------------------------------------------------------- /shinysdr/i/test_blocks.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017, 2018 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.internet import defer 21 | from twisted.internet import reactor as the_reactor 22 | from twisted.internet.task import deferLater 23 | from twisted.trial import unittest 24 | 25 | from gnuradio import blocks 26 | from gnuradio import gr 27 | from gnuradio.fft import window as windows 28 | import numpy 29 | 30 | from shinysdr.i.blocks import Context, MonitorSink, ReactorSink, RecursiveLockBlockMixin 31 | from shinysdr.signals import SignalType 32 | 33 | 34 | class TestReactorSink(unittest.TestCase): 35 | def setUp(self): 36 | self.tb = gr.top_block(str('TestReactorSink')) # py2/3 compatibility -- must be the 'normal' string type in either case 37 | self.out = [] 38 | 39 | def callback(self, array): 40 | self.out.append(array.tolist()) 41 | 42 | @defer.inlineCallbacks 43 | def test_bytes(self): 44 | test_data_bytes = [1, 2, 3, 255] 45 | self.tb.connect( 46 | blocks.vector_source_b(test_data_bytes), 47 | ReactorSink(numpy_type=numpy.uint8, callback=self.callback, reactor=the_reactor)) 48 | self.tb.start() 49 | self.tb.wait() 50 | self.tb.stop() 51 | yield deferLater(the_reactor, 0.0, lambda: None) 52 | self.assertEqual(self.out, [test_data_bytes]) 53 | 54 | @defer.inlineCallbacks 55 | def test_pair_floats(self): 56 | # This test isn't about complexes, but this is the easiest way to set up the vector source 57 | test_data_floats = [[1, 2], [3, 4]] 58 | test_data_complexes = [complex(1, 2), complex(3, 4)] 59 | self.tb.connect( 60 | blocks.vector_source_c(test_data_complexes), 61 | ReactorSink(numpy_type=numpy.dtype((numpy.float32, 2)), callback=self.callback, reactor=the_reactor)) 62 | self.tb.start() 63 | self.tb.wait() 64 | self.tb.stop() 65 | yield deferLater(the_reactor, 0.0, lambda: None) 66 | self.assertEqual(self.out, [test_data_floats]) 67 | 68 | 69 | class TestMonitorSink(unittest.TestCase): 70 | def setUp(self): 71 | self.tb = RLTB() 72 | self.context = Context(self.tb) 73 | 74 | def make(self, kind='IQ'): 75 | signal_type = SignalType(kind=kind, sample_rate=1000) 76 | m = MonitorSink( 77 | context=self.context, 78 | signal_type=signal_type) 79 | self.tb.connect(blocks.null_source(signal_type.get_itemsize()), m) 80 | return m 81 | 82 | def test_smoke_complex(self): 83 | self.make('IQ') 84 | self.tb.start() 85 | self.tb.stop() 86 | self.tb.wait() 87 | 88 | def test_smoke_real(self): 89 | self.make('MONO') 90 | self.tb.start() 91 | self.tb.stop() 92 | self.tb.wait() 93 | 94 | def test_smoke_change_window(self): 95 | m = self.make() 96 | self.tb.start() 97 | m.set_window_type(windows.WIN_FLATTOP) 98 | self.tb.stop() 99 | self.tb.wait() 100 | 101 | 102 | class RLTB(gr.top_block, RecursiveLockBlockMixin): 103 | pass 104 | -------------------------------------------------------------------------------- /shinysdr/i/test_dependencies_cases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpreid/shinysdr/25022d36903ff67e036e82a22b6555a12a4d8e8a/shinysdr/i/test_dependencies_cases/__init__.py -------------------------------------------------------------------------------- /shinysdr/i/test_dependencies_cases/imports.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | # pylint: disable=import-error, no-member, no-name-in-module 3 | import shinysdr.nonexistent_module_in_dep 4 | print(shinysdr.nonexistent_module_in_dep) 5 | -------------------------------------------------------------------------------- /shinysdr/i/test_dependencies_cases/misc.py: -------------------------------------------------------------------------------- 1 | raise Exception('boo') 2 | -------------------------------------------------------------------------------- /shinysdr/i/test_json.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from collections import namedtuple 21 | 22 | from twisted.trial import unittest 23 | 24 | from shinysdr.i.json import serialize, transform_for_json 25 | 26 | 27 | SomeNamedTuple = namedtuple('SomeNamedTuple', ['x', 'y']) 28 | 29 | 30 | class TestSerialize(unittest.TestCase): 31 | def test_smoke(self): 32 | self.assertEqual('"foo"', serialize('foo')) 33 | 34 | 35 | class TestTransformForJson(unittest.TestCase): 36 | def test_default(self): 37 | value = {'things': [1, '2', None, True, False]} 38 | self.assertEqual(transform_for_json(value), value) 39 | 40 | def test_tuple(self): 41 | """Test that the namedtuple special case does not affect regular tuples, other than by turning them into lists.""" 42 | self.assertEqual(transform_for_json((1, (2, 3))), [1, [2, 3]]) 43 | 44 | def test_namedtuple(self): 45 | self.assertEqual( 46 | transform_for_json([SomeNamedTuple([1], SomeNamedTuple(2, 3))]), 47 | [{'x': [1], 'y': {'x': 2, 'y': 3}}]) 48 | -------------------------------------------------------------------------------- /shinysdr/i/test_math.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, 2016 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from math import pi 21 | 22 | from twisted.trial import unittest 23 | 24 | import shinysdr.i.math as smath 25 | 26 | 27 | class TestFactorize(unittest.TestCase): 28 | longMessages = True 29 | 30 | def test_error(self): 31 | self.assertRaises(ValueError, lambda: smath.factorize(0)) 32 | 33 | def test_cases(self): 34 | self.assertEqual(smath.factorize(1), []) 35 | self.assertEqual(smath.factorize(2), [2]) 36 | self.assertEqual(smath.factorize(3), [3]) 37 | self.assertEqual(smath.factorize(4), [2, 2]) 38 | self.assertEqual(smath.factorize(5), [5]) 39 | self.assertEqual(smath.factorize(6), [2, 3]) 40 | self.assertEqual(smath.factorize(7), [7]) 41 | self.assertEqual(smath.factorize(8), [2, 2, 2]) 42 | self.assertEqual(smath.factorize(9), [3, 3]) 43 | self.assertEqual(smath.factorize(48000), [2] * 7 + [3] + [5] * 3) 44 | 45 | 46 | class TestSmallFactorAtLeast(unittest.TestCase): 47 | longMessages = True 48 | 49 | def test_exact(self): 50 | self.assertEqual(smath.small_factor_at_least(100, 9), 10) 51 | self.assertEqual(smath.small_factor_at_least(100, 10), 10) 52 | self.assertEqual(smath.small_factor_at_least(100, 11), 20) 53 | 54 | def test_approx(self): 55 | self.assertEqual(smath.small_factor_at_least(100, 9, _force_approx=True), 25) 56 | self.assertEqual(smath.small_factor_at_least(100, 10, _force_approx=True), 10) 57 | self.assertEqual(smath.small_factor_at_least(100, 11, _force_approx=True), 25) 58 | 59 | 60 | class TestSphericalMath(unittest.TestCase): 61 | def test_geodesic_distance(self): 62 | self.assertApproximates( 63 | smath.geodesic_distance((0, 0), (0, 180)), 64 | smath._EARTH_MEAN_RADIUS_METERS * pi, 65 | 1e-8) 66 | self.assertApproximates( 67 | smath.geodesic_distance((0, 0), (0, 90)), 68 | smath._EARTH_MEAN_RADIUS_METERS * pi / 2, 69 | 1e-8) 70 | -------------------------------------------------------------------------------- /shinysdr/i/test_modes.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.trial import unittest 21 | 22 | from shinysdr.i.modes import _ModeTable 23 | from shinysdr.types import EnumRow 24 | from . import test_modes_cases as package 25 | 26 | 27 | class TestModeTable(unittest.TestCase): 28 | table = _ModeTable(package) 29 | 30 | def test_get_modes_all(self): 31 | self.assertEqual( 32 | {d.mode for d in self.table.get_modes(include_unavailable=True)}, 33 | {'available', 'unavailable'}) 34 | 35 | def test_get_modes_available(self): 36 | self.assertEqual( 37 | {d.mode for d in self.table.get_modes(include_unavailable=False)}, 38 | {'available'}) 39 | 40 | def test_lookup_and_list_contents(self): 41 | modes = self.table.get_modes(include_unavailable=False) 42 | mode_def = self.table.lookup_mode('available', include_unavailable=False) 43 | self.assertEqual(modes[0], mode_def) 44 | self.assertEqual(mode_def.mode, 'available') 45 | self.assertEqual(mode_def.info, EnumRow(label='expected available')) 46 | self.assertEqual(mode_def.mod_class, None) 47 | self.assertEqual(mode_def.unavailability, None) 48 | -------------------------------------------------------------------------------- /shinysdr/i/test_modes_cases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpreid/shinysdr/25022d36903ff67e036e82a22b6555a12a4d8e8a/shinysdr/i/test_modes_cases/__init__.py -------------------------------------------------------------------------------- /shinysdr/i/test_modes_cases/available_unavailable.py: -------------------------------------------------------------------------------- 1 | from shinysdr.interfaces import ModeDef 2 | from shinysdr.types import EnumRow 3 | 4 | plugin_available = ModeDef(mode='available', 5 | info=EnumRow(label='expected available'), 6 | demod_class=object(), 7 | unavailability=None) 8 | 9 | plugin_unavailable = ModeDef(mode='unavailable', 10 | info=EnumRow(label='expected unavailable'), 11 | demod_class=object(), 12 | unavailability='For testing.') 13 | -------------------------------------------------------------------------------- /shinysdr/i/test_receiver.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.trial import unittest 21 | 22 | from shinysdr.i.modes import lookup_mode 23 | from shinysdr.i.top import Top 24 | from shinysdr.plugins.simulate import SimulatedDevice 25 | from shinysdr.testutil import state_smoke_test 26 | 27 | 28 | class TestReceiver(unittest.TestCase): 29 | def setUp(self): 30 | self.top = Top(devices={'s1': SimulatedDevice()}) 31 | (_key, self.receiver) = self.top.add_receiver('AM', key='a') 32 | 33 | def tearDown(self): 34 | self.top.close_all_devices() 35 | 36 | def test_smoke(self): 37 | state_smoke_test(self.receiver) 38 | 39 | def test_no_audio_demodulator(self): 40 | """Smoke test for demodulator with no audio output.""" 41 | # TODO: Allow parameterizing with a different mode table so that we can use a test stub mode rather than a real one. Also fix rtl_433 leaving unclean reactor. 42 | for mode in ['MODE-S']: 43 | if lookup_mode(mode): 44 | self.receiver.set_mode(mode) 45 | break 46 | else: 47 | raise unittest.SkipTest('No no-audio mode available.') 48 | -------------------------------------------------------------------------------- /shinysdr/i/test_roots.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.trial import unittest 21 | from zope.interface import implementer 22 | 23 | from shinysdr.i.roots import CapTable, IEntryPoint 24 | from shinysdr.values import ExportedState, exported_value, unserialize_exported_state 25 | 26 | 27 | class TestCapTable(unittest.TestCase): 28 | def setUp(self): 29 | self.t = CapTable(self.__unserializer) 30 | 31 | def __unserializer(self, state): 32 | return unserialize_exported_state(BaseEntryPointStub, state) 33 | 34 | def test_slug(self): 35 | cap = self.t.add(BaseEntryPointStub(), slug='foo') 36 | self.assertSubstring('foo-', cap) 37 | 38 | def test_persistence(self): 39 | stub = BaseEntryPointStub() 40 | cap = self.t.add(stub) 41 | state = self.t.as_persistable().state_to_json() 42 | self.tearDown() 43 | self.setUp() 44 | self.t.as_persistable().state_from_json(state) 45 | self.assertEqual(stub.get_serial_number(), self.t.as_unenumerable_collection()[cap].get_serial_number()) 46 | 47 | def test_deletion(self): 48 | stub = DeletableStub() 49 | cap = self.t.add(stub) 50 | self.assertTrue(cap in self.t.as_unenumerable_collection()) 51 | 52 | # hidden when deleted flag is set 53 | stub.set_deleted(True) 54 | self.assertFalse(cap in self.t.as_unenumerable_collection()) 55 | 56 | # but not actually removed (to avoid iterate/delete conflicts) 57 | stub.set_deleted(False) 58 | self.assertTrue(cap in self.t.as_unenumerable_collection()) 59 | 60 | # and actually removed after a garbage_collect 61 | stub.set_deleted(True) 62 | self.t.garbage_collect() 63 | stub.set_deleted(False) 64 | self.assertFalse(cap in self.t.as_unenumerable_collection()) 65 | 66 | 67 | # pylint: disable=global-statement 68 | # We need unique identifiers that are persistable, otherwise just fresh objects would do. 69 | _counter = 0 70 | 71 | 72 | @implementer(IEntryPoint) 73 | class BaseEntryPointStub(ExportedState): 74 | def __init__(self, serial_number=None): 75 | global _counter 76 | if serial_number is None: 77 | serial_number = _counter 78 | _counter += 1 79 | self.__serial_number = serial_number 80 | 81 | def get_type(self): 82 | return 'footype' 83 | 84 | def get_entry_point_slug(self): 85 | return '' 86 | 87 | def entry_point_is_deleted(self): 88 | return False 89 | 90 | def entry_point_what_to_do(self): 91 | raise NotImplementedError() 92 | 93 | @exported_value(type=int, persists=True, parameter='serial_number', changes='never') 94 | def get_serial_number(self): 95 | return self.__serial_number 96 | 97 | 98 | class DeletableStub(BaseEntryPointStub): 99 | __deleted = False 100 | 101 | def set_deleted(self, value): 102 | self.__deleted = value 103 | 104 | def entry_point_is_deleted(self): 105 | return self.__deleted 106 | 107 | def entry_point_what_to_do(self): 108 | raise NotImplementedError() 109 | -------------------------------------------------------------------------------- /shinysdr/i/test_session.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.web.resource import IResource 21 | from twisted.internet import reactor as the_reactor 22 | from twisted.trial import unittest 23 | 24 | from shinysdr.i.db import DatabaseModel 25 | from shinysdr.i.network.base import WebServiceCommon 26 | from shinysdr.i.session import Session 27 | from shinysdr.i.top import Top 28 | from shinysdr.plugins.simulate import SimulatedDevice 29 | from shinysdr.testutil import state_smoke_test 30 | 31 | 32 | class TestSession(unittest.TestCase): 33 | def setUp(self): 34 | self.session = Session( 35 | receive_flowgraph=Top(devices={'s1': SimulatedDevice()}), 36 | read_only_dbs={}, 37 | writable_db=DatabaseModel(the_reactor, {}, writable=True), 38 | features={}) 39 | 40 | def test_state_smoke(self): 41 | state_smoke_test(self.session) 42 | 43 | # TODO: Write more tests than this one of SessionResource linked to a real session 44 | def test_resource_smoke(self): 45 | IResource(self.session.get_entry_point_resource( 46 | wcommon=WebServiceCommon.stub(reactor=the_reactor))) 47 | -------------------------------------------------------------------------------- /shinysdr/i/webparts/block.template.xhtml: -------------------------------------------------------------------------------- 1 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |

Normal View

34 |
35 |
36 |
37 |
38 | 39 |

Inspector

40 |
41 |
42 |
43 |
44 | 45 | 46 | 57 | 58 | -------------------------------------------------------------------------------- /shinysdr/i/webparts/database-list.template.xhtml: -------------------------------------------------------------------------------- 1 | 19 | 23 | 24 | ShinySDR Databases 25 | 26 | 27 | 28 | 29 |
    30 |
  • 31 |
32 | 33 | -------------------------------------------------------------------------------- /shinysdr/i/webparts/error-page.template.xhtml: -------------------------------------------------------------------------------- 1 | 19 | 23 | 24 | Error: <t:transparent t:render='details_text'/> 25 | 26 | 27 | 28 | 29 |

Error

30 |

31 | 32 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/audio/util.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2020 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | define([], () => { 21 | const exports = {}; 22 | 23 | // webkitAudioContext required for Safari as of version 10.1 24 | const AudioContext = (window.AudioContext || window.webkitAudioContext); 25 | exports.AudioContext = AudioContext; 26 | 27 | // Given a maximum acceptable delay, calculate the largest power-of-two buffer size which does not result in more than that delay. 28 | function delayToBufferSize(sampleRate, maxDelayInSeconds) { 29 | const maxBufferSize = sampleRate * maxDelayInSeconds; 30 | let powerOfTwoBufferSize = 1 << Math.floor(Math.log(maxBufferSize) / Math.LN2); 31 | // Size limits defined by the Web Audio API specification. 32 | powerOfTwoBufferSize = Math.max(256, Math.min(16384, powerOfTwoBufferSize)); 33 | return powerOfTwoBufferSize; 34 | } 35 | exports.delayToBufferSize = delayToBufferSize; 36 | 37 | function showPlayPrompt() { 38 | const dialog = document.createElement('dialog'); 39 | dialog.classList.add('unspecific-modal-dialog'); 40 | // TODO class for styling 41 | 42 | dialog.textContent = 'This page provides a visualization of audio from your microphone, other available audio input device, or a file. It does not play any sound and does not record or transmit the audio or any derived information. (Or so this text claims.)'; 43 | 44 | const ackButton = dialog.appendChild(document.createElement('p')) 45 | .appendChild(document.createElement('button')); 46 | ackButton.textContent = 'Continue'; 47 | 48 | return new Promise((resolve, reject) => { 49 | ackButton.addEventListener('click', event => { 50 | dialog.close(); 51 | resolve(); 52 | }, true); 53 | dialog.addEventListener('close', event => { 54 | if (dialog.parentNode) { 55 | dialog.parentNode.removeChild(dialog); 56 | } 57 | reject(new Error('User canceled')); 58 | }, true); 59 | 60 | document.body.appendChild(dialog); 61 | requestAnimationFrame(() => { 62 | // Do this async so that if the browser doesn't support

features, they will still get a functional OK button. 63 | dialog.showModal(); 64 | }); 65 | }); 66 | } 67 | 68 | // Cooperate with the Chrome autoplay policy, which says that audio contexts cannot be in 'running' state until a user interaction with the page (or prior interaction with the site). 69 | // https://developers.google.com/web/updates/2017/09/autoplay-policy-changes 70 | // The default UI prompt is intended for the standalone "audio toolbox" pages. 71 | function audioContextAutoplayHelper(audioContext, showUI=showPlayPrompt) { 72 | if (audioContext.state === 'suspended') { 73 | console.log('audioContextAutoplayHelper: prompting'); 74 | return showUI().then(() => { 75 | console.log('audioContextAutoplayHelper: resuming after prompt'); 76 | return audioContext.resume().then(() => { 77 | console.log('audioContextAutoplayHelper: successfully resumed'); 78 | }); 79 | }); 80 | } else { 81 | console.log('audioContextAutoplayHelper: state is OK:', audioContext.state); 82 | return Promise.resolve(); 83 | } 84 | } 85 | exports.audioContextAutoplayHelper = audioContextAutoplayHelper; 86 | 87 | return Object.freeze(exports); 88 | }); 89 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/client-configuration-module.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, 2019 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | // This module is basically a shim for the server's client-configuration resource to be loaded as a module (and parsed only once). 19 | 20 | 'use strict'; 21 | 22 | define(['text!client-configuration.json'], (text) => { 23 | const exports = {}; 24 | 25 | const clientConfiguration = JSON.parse(text); 26 | const pluginIndex = clientConfiguration.plugins; 27 | const moduleIds = Object.freeze(Array.prototype.slice.call(pluginIndex.js)); 28 | 29 | const modeTable = Object.create(null); 30 | for (const k in pluginIndex.modes) { 31 | modeTable[k] = Object.freeze(pluginIndex.modes[k]); 32 | } 33 | Object.freeze(modeTable); 34 | 35 | exports.loadCSS = function () { 36 | Array.prototype.forEach.call(pluginIndex.css, cssUrl => { 37 | const link = document.createElement('link'); 38 | link.rel = 'stylesheet'; 39 | link.href = String(cssUrl); 40 | document.querySelector('head').appendChild(link); 41 | }); 42 | }; 43 | 44 | exports.getJSModuleIds = function () { 45 | return moduleIds; 46 | }; 47 | 48 | exports.getModeTable = function () { 49 | return modeTable; 50 | }; 51 | 52 | exports.getSharedTestObjectsURL = () => clientConfiguration.shared_test_objects_url; 53 | 54 | return Object.freeze(exports); 55 | }); 56 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/icon/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpreid/shinysdr/25022d36903ff67e036e82a22b6555a12a4d8e8a/shinysdr/i/webstatic/client/icon/icon-32.png -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/icon/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 61 | 62 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/map/basemap.geojson.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpreid/shinysdr/25022d36903ff67e036e82a22b6555a12a4d8e8a/shinysdr/i/webstatic/client/map/basemap.geojson.gz -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/map/curves-f.glsl: -------------------------------------------------------------------------------- 1 | varying lowp vec3 v_texcoordAndOpacity; 2 | varying mediump vec4 v_pickingColor; 3 | uniform sampler2D labels; 4 | uniform bool picking; 5 | 6 | void main(void) { 7 | lowp float lineBrightness = v_texcoordAndOpacity.x; 8 | lowp float opacity = v_texcoordAndOpacity.z; 9 | gl_FragColor = picking ? v_pickingColor : v_texcoordAndOpacity.z * vec4(vec3(1.0 - lineBrightness), 1.0); 10 | } 11 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/map/features-v.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 position; 2 | attribute vec4 velocityAndTimestamp; 3 | attribute vec3 billboard; 4 | attribute vec3 texcoordAndOpacity; 5 | attribute vec4 pickingColor; 6 | uniform highp float time; 7 | uniform mat4 projection; 8 | uniform vec3 billboardScale; 9 | varying lowp vec3 v_texcoordAndOpacity; 10 | varying mediump vec4 v_pickingColor; // TODO figure out necessary resolution 11 | 12 | void main(void) { 13 | vec3 velocity = velocityAndTimestamp.xyz; 14 | highp float timestamp = velocityAndTimestamp.w; 15 | highp float speed = length(velocity); 16 | vec3 forward = speed > 0.0 ? normalize(velocity) : vec3(0.0); 17 | highp float distance = speed * (time - timestamp); 18 | vec3 currentPosition = cos(distance) * position + sin(distance) * forward; 19 | gl_Position = vec4(currentPosition, 1.0) * projection + vec4(billboard * billboardScale, 0.0); 20 | v_texcoordAndOpacity = texcoordAndOpacity; 21 | v_pickingColor = pickingColor; 22 | velocity; 23 | } 24 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/map/icons/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/map/icons/station-generic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/map/icons/station-user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 17 | 18 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/map/points-f.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2015, 2016, 2017, 2019 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | // WebGL fragment shader for drawing the map's features. 19 | 20 | // Packed vector: Texture's UV coordinates (2) and overall opacity (1). 21 | varying lowp vec3 v_texcoordAndOpacity; 22 | 23 | // Color to draw for picking purposes; not modified by opacity. 24 | varying mediump vec4 v_pickingColor; 25 | 26 | // Texture to draw; premultiplied alpha. 27 | uniform sampler2D labels; 28 | 29 | // Whether we are drawing in picking mode (draw v_pickingColor instead of the texture). 30 | uniform bool picking; 31 | 32 | void main(void) { 33 | lowp vec2 texcoord = v_texcoordAndOpacity.xy; 34 | lowp float opacity = v_texcoordAndOpacity.z; 35 | 36 | // Draw either the picking color or the texture. 37 | gl_FragColor = picking ? v_pickingColor : opacity * texture2D(labels, texcoord); 38 | 39 | // If not picking, discard nearly invisible areas; if picking, the entire area we're drawing counts unless it's nearly invisible (so that clicking on holes inside text labels counts). 40 | if ((picking ? opacity : gl_FragColor.a) < 0.01) discard; 41 | } 42 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/map/sphere-f.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2015, 2016, 2019 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | // WebGL fragment shader for drawing the map's globe. 19 | 20 | // Position being drawn in 3-dimensional coordinates. 21 | varying highp vec3 v_position; 22 | 23 | // Position in 2-dimensional coordinates (longitude, latitude); used to texture the sphere. 24 | varying highp vec2 v_lonlat; 25 | 26 | // Texture drawn on the sphere, in plate carrée projection. 27 | uniform sampler2D texture; 28 | 29 | // Position of the sun, in the same coordinates as v_position. 30 | uniform lowp vec3 sun; 31 | 32 | void main(void) { 33 | // Get surface color from texture by transforming v_lonlat into texture coordinates. 34 | lowp vec4 texture = texture2D(texture, 35 | mod(v_lonlat + vec2(180.0, 90.0), 360.0) * vec2(1.0/360.0, -1.0/180.0) 36 | + vec2(0.0, 1.0)); 37 | 38 | // Compute sunlight magnitude (not realistic, just enough to get a day side, night side, and terminator). 39 | lowp float light = mix(1.0, clamp(dot(v_position, sun) * 10.0 + 1.0, 0.0, 1.0), 0.25); 40 | 41 | gl_FragColor = vec4(texture.rgb * light, texture.a); 42 | } 43 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/map/sphere-v.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2015, 2016, 2019 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | // WebGL vertex shader for drawing the map's globe. 19 | 20 | // Position in 3-dimensional coordinates. 21 | attribute mediump vec3 position; 22 | varying highp vec3 v_position; 23 | 24 | // Position in 2-dimensional coordinates (longitude, latitude); used to texture the sphere. 25 | attribute highp vec2 lonlat; 26 | varying highp vec2 v_lonlat; 27 | 28 | // Camera projection. 29 | uniform highp mat4 projection; 30 | 31 | void main(void) { 32 | gl_Position = vec4(position, 1.0) * projection; 33 | v_lonlat = lonlat; 34 | v_position = position; 35 | } 36 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/math.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2014, 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | define(() => { 21 | const exports = {}; 22 | 23 | // true modulo, not % 24 | function mod(value, modulus) { 25 | return (value % modulus + modulus) % modulus; 26 | } 27 | exports.mod = mod; 28 | 29 | // Convert dB to factor. 30 | function dB(x) { 31 | return Math.pow(10, 0.1 * x); 32 | } 33 | exports.dB = dB; 34 | 35 | function formatFreqMHz(freq) { 36 | return (freq / 1e6).toFixed(2); 37 | } 38 | exports.formatFreqMHz = formatFreqMHz; 39 | 40 | // "exact" as in doesn't drop digits. Used in frequency scale. 41 | function formatFreqExact(freq) { 42 | freq = +freq; 43 | const absoluteFreq = Math.abs(freq); 44 | if (absoluteFreq < 1e3 || !isFinite(freq)) { 45 | return String(freq); 46 | } else if (absoluteFreq < 1e6) { 47 | return freq / 1e3 + 'k'; 48 | } else if (absoluteFreq < 1e9) { 49 | return freq / 1e6 + 'M'; 50 | } else { 51 | return freq / 1e9 + 'G'; 52 | } 53 | } 54 | exports.formatFreqExact = formatFreqExact; 55 | 56 | // Format with dropping digits likely not cared about, and units. Used in receiver frequency marks. 57 | function formatFreqInexactVerbose(freq) { 58 | freq = +freq; 59 | const absoluteFreq = Math.abs(freq); 60 | let prefix; 61 | if (absoluteFreq < 1e3 || !isFinite(freq)) { 62 | prefix = ''; 63 | } else if (absoluteFreq < 1e6) { 64 | freq /= 1e3; 65 | prefix = 'k'; 66 | } else if (absoluteFreq < 1e9) { 67 | freq /= 1e6; 68 | prefix = 'M'; 69 | } else { 70 | freq /= 1e9; 71 | prefix = 'G'; 72 | } 73 | let freqText = freq.toFixed(3); 74 | // toFixed rounds, but also adds zeros; we want only the rounding. 75 | freqText = freqText.replace(/([0-9])0+$/, '$1'); 76 | return freqText + ' ' + prefix + 'Hz'; 77 | } 78 | exports.formatFreqInexactVerbose = formatFreqInexactVerbose; 79 | 80 | return Object.freeze(exports); 81 | }); 82 | 83 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/menus.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, 2016, 2018 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | // Manages pop-up context menus 19 | 20 | 'use strict'; 21 | 22 | define([ 23 | './types', 24 | './values', 25 | './widget', 26 | ], ( 27 | import_types, 28 | import_values, 29 | import_widget 30 | ) => { 31 | const { 32 | anyT, 33 | } = import_types; 34 | const { 35 | ConstantCell, 36 | } = import_values; 37 | const { 38 | createWidgetExt, 39 | } = import_widget; 40 | 41 | const exports = {}; 42 | 43 | const menuDialog = new WeakMap(); 44 | const menuInner = new WeakMap(); 45 | class Menu { 46 | constructor(widgetContext, widgetCtor, target) { 47 | const dialog = document.createElement('dialog'); 48 | const innerElement = document.createElement('div'); 49 | dialog.appendChild(innerElement); 50 | dialog.classList.add('menu-dialog'); 51 | 52 | menuDialog.set(this, dialog); 53 | menuInner.set(this, innerElement); 54 | 55 | const menuContext = widgetContext.forMenu(function closeCallback() { 56 | dialog.close(); 57 | }); 58 | 59 | const widgetHandle = createWidgetExt(menuContext, widgetCtor, innerElement, new ConstantCell(target, anyT)); 60 | 61 | dialog.addEventListener('mouseup', event => { 62 | if (event.target === dialog) { // therefore not on content 63 | dialog.close(); 64 | } 65 | event.stopPropagation(); 66 | }, true); 67 | dialog.addEventListener('close', event => { 68 | if (dialog.parentNode) { 69 | dialog.parentNode.removeChild(dialog); 70 | } 71 | widgetHandle.destroy(); // TODO prevent reuse at this point 72 | }, true); 73 | 74 | Object.freeze(this); 75 | } 76 | 77 | openAt(targetElOrEvent) { 78 | const dialog = menuDialog.get(this); 79 | dialog.ownerDocument.body.appendChild(dialog); 80 | // TODO: Per spec, aligning the dialog should be automatic if we pass the target to showModal, but it isn't in Chrome 47. Enable once it works properly (and rework the kludge for map features to be compatible). 81 | dialog.showModal(/* targetElOrEvent */); 82 | if (targetElOrEvent && targetElOrEvent.nodeType) { 83 | const dialogCR = dialog.getBoundingClientRect(); 84 | const targetCR = targetElOrEvent.getBoundingClientRect(); 85 | dialog.style.left = ( 86 | Math.max(0, Math.min(document.body.clientWidth - dialogCR.width, 87 | targetCR.left + targetCR.width / 2 - dialogCR.width / 2)) 88 | ) + 'px'; 89 | dialog.style.top = ( 90 | Math.max(0, Math.min(document.body.clientHeight - dialogCR.height, 91 | targetCR.bottom)) 92 | ) + 'px'; 93 | } 94 | //lifecycleInit(menuInner.get(this)); // TODO make this possible or make it unavoidable (widget does this implicitly in 0ms but menu can be delayed between create and open) 95 | } 96 | 97 | close() { 98 | menuDialog.get(this).close(); 99 | } 100 | } 101 | exports.Menu = Menu; 102 | 103 | return Object.freeze(exports); 104 | }); 105 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/themes/black.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Kevin Reid and the ShinySDR contributors 2 | * 3 | * This file is part of ShinySDR. 4 | * 5 | * ShinySDR is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * ShinySDR is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with ShinySDR. If not, see . 17 | */ 18 | 19 | :root { 20 | --shinysdr-theme-foreground: #FFF; 21 | --shinysdr-theme-halo-bgcolor: black; 22 | 23 | --shinysdr-theme-empty-bgcolor: #333; 24 | --shinysdr-theme-empty-bgfill: linear-gradient(78deg, 25 | #2F2F2F 26%, 26 | #333 26%, 27 | #333 51%, 28 | #2F2F2F 51%, 29 | #2F2F2F 76%, 30 | #333 76%); 31 | 32 | --shinysdr-theme-column-bgcolor: #222; 33 | --shinysdr-theme-column-bgfill: #222; 34 | 35 | --shinysdr-theme-window-title-foreground: white; 36 | --shinysdr-theme-window-title-bgcolor: #382828; 37 | --shinysdr-theme-window-title-bgfill: linear-gradient(to bottom, #433 0%, #322 100%); 38 | 39 | --shinysdr-theme-header-bgfill: #382828; 40 | --shinysdr-theme-header-bgcolor: #382828; 41 | 42 | --shinysdr-theme-spectrum-bgcolor: black; 43 | --shinysdr-theme-spectrum-label-bgcolor: #337; /* tmp */ 44 | --shinysdr-theme-spectrum-label-bgfill: #337; 45 | --shinysdr-theme-spectrum-label-foreground: white; 46 | 47 | --shinysdr-theme-knob-bgcolor: black; 48 | --shinysdr-theme-knob-foreground: white; 49 | 50 | --shinysdr-theme-databox-bgcolor: black; 51 | --shinysdr-theme-databox-foreground: white; 52 | --shinysdr-theme-databox-disabled-foreground: gray; 53 | --shinysdr-theme-databox-hover-bgcolor: #222; 54 | --shinysdr-theme-databox-band-start-bgfill: linear-gradient(to bottom, 55 | #444 0%, 56 | var(--shinysdr-theme-databox-bgcolor) .6em); 57 | --shinysdr-theme-databox-band-end-bgfill: linear-gradient(to top, 58 | #222 0%, 59 | var(--shinysdr-theme-databox-bgcolor) .6em); 60 | } 61 | 62 | :link { 63 | color: #99F; 64 | } 65 | :visited { 66 | color: #F9F; 67 | } 68 | 69 | /* TODO: add disabled/readonly styles which we might be stepping on */ 70 | button, select, meter { 71 | border-color: #666; 72 | background-color: var(--shinysdr-theme-column-bgcolor); 73 | color: var(--shinysdr-theme-databox-foreground); 74 | } 75 | input, textarea { 76 | border-color: #666; 77 | background-color: var(--shinysdr-theme-databox-bgcolor); 78 | color: var(--shinysdr-theme-databox-foreground); 79 | } 80 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/themes/gray.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2016 Kevin Reid and the ShinySDR contributors 2 | * 3 | * This file is part of ShinySDR. 4 | * 5 | * ShinySDR is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * ShinySDR is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with ShinySDR. If not, see . 17 | */ 18 | 19 | :root { 20 | --shinysdr-theme-foreground: black; 21 | --shinysdr-theme-halo-bgcolor: white; 22 | 23 | --shinysdr-theme-empty-bgcolor: #777; 24 | --shinysdr-theme-empty-bgfill: linear-gradient(78deg, 25 | #888888 26%, 26 | #828282 26%, 27 | #828282 51%, 28 | #888888 51%, 29 | #888888 76%, 30 | #828282 76%); 31 | 32 | --shinysdr-theme-column-bgcolor: #B9B9B9; 33 | --shinysdr-theme-column-bgfill: linear-gradient(to right, #BCBCBC 0%,#B5B5B5 100%); 34 | 35 | --shinysdr-theme-window-title-foreground: black; 36 | --shinysdr-theme-window-title-bgcolor: #D8D8D8; 37 | --shinysdr-theme-window-title-bgfill: linear-gradient(to bottom, #FFF 0%,#CCC 100%); 38 | 39 | --shinysdr-theme-header-bgcolor: #DDD; 40 | --shinysdr-theme-header-bgfill: linear-gradient(to bottom, #FFF 0,#DDD 0.15em); 41 | 42 | --shinysdr-theme-spectrum-bgcolor: black; 43 | --shinysdr-theme-spectrum-label-bgcolor: #555; 44 | --shinysdr-theme-spectrum-label-bgfill: linear-gradient(to right, #666 0%,#444 100%); 45 | --shinysdr-theme-spectrum-label-foreground: white; 46 | 47 | --shinysdr-theme-knob-bgcolor: black; 48 | --shinysdr-theme-knob-foreground: white; 49 | 50 | --shinysdr-theme-databox-bgcolor: white; 51 | --shinysdr-theme-databox-foreground: black; 52 | --shinysdr-theme-databox-disabled-foreground: gray; 53 | --shinysdr-theme-databox-hover-bgcolor: #DDD; 54 | --shinysdr-theme-databox-band-start-bgfill: linear-gradient(to bottom, 55 | #DDD 0%, 56 | var(--shinysdr-theme-databox-bgcolor) .6em); 57 | --shinysdr-theme-databox-band-end-bgfill: linear-gradient(to top, 58 | #CCC 0%, 59 | var(--shinysdr-theme-databox-bgcolor) .6em); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/widgets.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2014, 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | define([ 21 | './widgets/appui', 22 | './widgets/basic', 23 | './widgets/dbui', 24 | './widgets/scope', 25 | './widgets/spectrum', 26 | ], ( 27 | widgets_appui, 28 | widgets_basic, 29 | widgets_dbui, 30 | widgets_scope, 31 | widgets_spectrum 32 | ) => { 33 | // TODO: This module is leftover from refactoring and only makes the namespace used for looking up widgets by name -- this ought to become something else that better considers plugin extensibility. 34 | 35 | const widgets = Object.create(null); 36 | Object.assign(widgets, widgets_appui, widgets_basic, widgets_dbui, widgets_spectrum); 37 | for (const k in {ScopeControls: 0, ScopePlot: 0}) { 38 | // special case because this module exports non-widgets 39 | widgets[k] = widgets_scope[k]; 40 | } 41 | 42 | // TODO: This is currently used by plugins to extend the widget namespace. Create a non-single-namespace widget type lookup and then freeze this. 43 | return widgets; 44 | }); 45 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/widgets/scope-f.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2014, 2016 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | varying lowp float v_z; 19 | uniform mediump float persistence_gamma; 20 | void main(void) { 21 | // TODO: Experiment with ways we can use the currently-wasted three different components. 22 | // Note: the pow() here (rather than exponential decay) is not realistic but seems to produce good results. 23 | gl_FragColor = vec4(vec3(pow(v_z, persistence_gamma)), 1.0); 24 | } 25 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/widgets/scope-pp1.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2014, 2016 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | const int diameter = radius * 2 + 1; 19 | uniform mediump float kernel[diameter]; 20 | 21 | void main(void) { 22 | highp vec3 sum = vec3(0.0); 23 | for (int kx = 0; kx < diameter; kx++) { 24 | sum += kernel[kx] * texture2D(pp_texture, pp_texcoord + vec2(float(kx - radius), 0.0) / pp_size).rgb; 25 | } 26 | gl_FragColor = vec4(sum, 1.0); 27 | } 28 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/widgets/scope-pp2.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2014, 2016 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | const int diameter = radius * 2 + 1; 19 | uniform mediump float intensity; 20 | uniform mediump float invgamma; 21 | uniform mediump float kernel[diameter]; 22 | 23 | void main(void) { 24 | highp vec3 sum = vec3(0.0); 25 | for (int ky = 0; ky < diameter; ky++) { 26 | sum += kernel[ky] * texture2D(pp_texture, pp_texcoord + vec2(0.0, float(ky - radius)) / pp_size).rgb; 27 | } 28 | gl_FragColor = vec4(pow(intensity * sum, vec3(invgamma)) * vec3(0.1, 1.0, 0.5), 1.0); 29 | } 30 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/widgets/scope-v.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2014, 2016 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | attribute mediump float relativeTime; 19 | uniform float interpStep; 20 | uniform mat4 projection; 21 | uniform mediump float bufferCutPoint; 22 | uniform sampler2D scopeData; 23 | varying lowp float v_z; 24 | 25 | // vertex shader scraps for FIR filtering -- couldn't get it to work but this should still be the skeleton of it 26 | // uniform mediump float filter[37]; 27 | // mediump vec2 rawsignal(mediump float tsub) { // zero-stuffed signal 28 | // return mod(tsub / interpStep, 10.0) < 1.00 29 | // ? texture2D(scopeData, vec2(tsub, 0.5)).ra 30 | // : vec2(0.0); 31 | // } 32 | // for (int i = -18; i <= 18; i++) { 33 | // signal += filter[i] * rawsignal(time + float(i) * interpStep); 34 | // } 35 | 36 | void main(void) { 37 | mediump float bufferTime = mod(bufferCutPoint + relativeTime, 1.0); 38 | gl_PointSize = 1.0; 39 | mediump vec2 signal = texture2D(scopeData, vec2(bufferTime, 0.5)).ra; 40 | vec4 basePos = vec4(signal, relativeTime * 2.0 - 1.0, 1.0); 41 | vec4 projected = basePos * projection; 42 | gl_Position = vec4(clamp(projected.x, -0.999, 0.999), clamp(projected.y, -0.999, 0.999), 0.0, projected.w); // show over-range in x and y and don't clip to z 43 | v_z = (projected.z / projected.w) / 2.0 + 0.5; // 0-1 range instead of -1-1 44 | } 45 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/widgets/spectrum-common.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2019 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | // WebGL definitions part of both vertex and fragment shaders for the spectrum graph (panadapter) and waterfall displays. 19 | 20 | // Luminance texture storing spectrum data. S axis = frequency, T axis = time (as a circular buffer). 21 | uniform sampler2D spectrumDataTexture; 22 | 23 | // One-dimensional (width = 1) texture containing, for each line of the spectrumDataTexture, the center frequency of that line. 24 | // If USE_FLOAT_TEXTURE is true, this is a float-valued luminance texture. Otherwise, the value is packed into the 8-bit components in (msb)ABGR(lsb) order. 25 | uniform sampler2D centerFreqHistory; 26 | 27 | // The center frequency currently active; determines the center of the viewport in frequency space. 28 | uniform highp float currentFreq; 29 | 30 | // Horizontal axis scale. TODO: explain in what units. 31 | uniform mediump float freqScale; 32 | 33 | // Look up and unpack a value from centerFreqHistory. 34 | // point's first component is ignored and second component is the position in history. 35 | // TODO: Cross-reference how history positions are defined. 36 | highp float getFreqOffset(highp vec2 point) { 37 | // Coordinates for sampling centerFreqHistory. Must do our own wrapping because the wrap mode is CLAMP_TO_EDGE — TODO: Why can't we use REPEAT mode? 38 | point = vec2(0.0, mod(point.t, 1.0)); 39 | 40 | #if USE_FLOAT_TEXTURE 41 | // Direct lookup. 42 | highp float historyFreq = texture2D(centerFreqHistory, point).r; 43 | #else 44 | // Unpack from bytes. 45 | highp vec4 historyFreqVec = texture2D(centerFreqHistory, point); 46 | highp float historyFreq = 47 | (((historyFreqVec.a * 255.0 * 256.0 48 | + historyFreqVec.b * 255.0) * 256.0 49 | + historyFreqVec.g * 255.0) * 256.0 50 | + historyFreqVec.r * 255.0); 51 | #endif 52 | 53 | return currentFreq - historyFreq; 54 | } 55 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/widgets/spectrum-graph-v.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2015, 2016, 2019 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | // Vertex position, in normalized device coordinates. 19 | attribute vec4 position; 20 | 21 | // Zoom parameters. xZero is the texture-space left/lowest-frequency edge and xScale is the span; xZero and xZero + xScale are between 0 and 1. 22 | // TODO: The values 'xZero' used by the graph shader and 'xTranslate' used by the waterfall shader differ by a half-texel. Reconcile or name them better for clarity. 23 | uniform mediump float xZero, xScale; 24 | 25 | // Position of fragment in texture space (0 = lowest visible frequency, 1 = highest visible frequency) after zoom is applied. 26 | varying highp vec2 v_position; 27 | 28 | void main(void) { 29 | gl_Position = position; 30 | 31 | // Convert normalized device coordinates (-1...1) to texture coordinates (0...1). 32 | mediump vec2 basePos = (position.xy + vec2(1.0)) / 2.0; 33 | 34 | // Apply horizontal zoom. 35 | v_position = vec2(xScale * basePos.x + xZero, basePos.y); 36 | } 37 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/widgets/spectrum-waterfall-f.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2014, 2015, 2016, 2019 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | // RGBA lookup table for gradient colors. S axis unused, T axis = scaled value. 19 | uniform sampler2D gradient; 20 | 21 | // Scale and offset to map spectrumDataTexture values to 'gradient' texture coordinates. 22 | uniform mediump float gradientZero; 23 | uniform mediump float gradientScale; 24 | 25 | // Position of this fragment in spectrumDataTexture, before accounting for frequency offsets. 26 | varying mediump vec2 v_position; 27 | 28 | // Posible half-texel adjustment to align GL texture coordinates with where we want FFT bins to fall. 29 | uniform highp float textureRotation; 30 | 31 | void main(void) { 32 | highp vec2 texLookup = mod(v_position, 1.0); 33 | highp float freqOffset = getFreqOffset(texLookup) * freqScale; 34 | mediump vec2 shiftedDataCoordinates = texLookup + vec2(freqOffset, 0.0); 35 | 36 | if (shiftedDataCoordinates.x < 0.0 || shiftedDataCoordinates.x > 1.0) { 37 | // We're off the edge of the data; draw background instead of any data value. 38 | gl_FragColor = BACKGROUND_COLOR; 39 | } else { 40 | // Fetch data value. 41 | mediump float data = texture2D(spectrumDataTexture, shiftedDataCoordinates + vec2(textureRotation, 0.0)).r; 42 | 43 | // Fetch color from data texture. 44 | gl_FragColor = texture2D(gradient, vec2(0.5, gradientZero + gradientScale * data)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/client/widgets/spectrum-waterfall-v.glsl: -------------------------------------------------------------------------------- 1 | // Copyright 2013, 2015, 2016, 2019 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | // Vertex position, in normalized device coordinates. 19 | attribute vec4 position; 20 | 21 | // Position of fragment in texture space (0 = lowest visible frequency / lowest value, 1 = highest visible frequency / highest value) after zoom is applied. 22 | varying highp vec2 v_position; 23 | 24 | // T coordinate of the most recently written line in spectrumDataTexture (see spectrum-common.glsl). 25 | uniform highp float scroll; 26 | 27 | // Zoom parameters. xTranslate is the texture-space left/lowest-frequency edge and xScale is the span; xTranslate and xTranslate + xScale are between 0 and 1. 28 | // TODO: The values 'xZero' used by the graph shader and 'xTranslate' used by the waterfall shader differ by a half-texel. Reconcile or name them better for clarity -- or convert this shader to take a transformation matrix. 29 | uniform highp float xTranslate, xScale; 30 | 31 | // Vertical axis scale expressed as the display height divided by spectrumDataTexture's height. 32 | uniform highp float yScale; 33 | 34 | void main(void) { 35 | gl_Position = position; 36 | 37 | // TODO: Use a single input matrix instead of computing it on the fly -- if that turns out tidier. 38 | // Matrix converting normalized device coordinates to texture coordinates. 39 | mat3 viewToTexture = mat3(0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 1.0); 40 | // Matrices applying frequency-axis zoom and vertical pixel-oriented scale. 41 | // Both result in viewing a smaller portion of spectrumDataTexture. 42 | mat3 zoom = mat3(xScale, 0.0, 0.0, 0.0, 1.0, 0.0, xTranslate, 0.0, 1.0); 43 | mat3 applyYScale = mat3(1.0, 0.0, 0.0, 0.0, yScale, 0.0, 0.0, -yScale, 1.0); 44 | // Final combined matrix 45 | mat3 viewMatrix = applyYScale * zoom * viewToTexture; 46 | 47 | // Apply view matrix and buffer scrolling to determine coordinate system used by fragment shader. 48 | v_position = (viewMatrix * position.xyw).xy + vec2(0.0, scroll); 49 | } 50 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | ShinySDR 23 | 24 | 25 | 26 | 27 | 28 |

ShinySDR

29 | 30 |

This is a private ShinySDR installation. Use the URL provided on server startup to get access.

31 | 32 |
33 | 34 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/manual/index.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | ShinySDR Manual 23 | 24 | 25 | 26 | 27 | 28 |

ShinySDR Manual

29 | 30 |
    31 |
  1. Installation
  2. 32 |
  3. Configuration and devices
  4. 33 |
  5. Operation [incomplete]
  6. 34 |
  7. Frequency databases [incomplete]
  8. 35 |
  9. Troubleshooting & FAQ
  10. 36 |
  11. Programmer's guide [incomplete]
  12. 37 |
38 | 39 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/manual/manual-style.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2013, 2014, 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | * 3 | * This file is part of ShinySDR. 4 | * 5 | * ShinySDR is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * ShinySDR is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with ShinySDR. If not, see . 17 | */ 18 | 19 | @import url(../client/ui.css); 20 | @import url(../client/themes/gray.css); 21 | 22 | html { 23 | margin: 0; 24 | } 25 | 26 | body { 27 | margin: 0 auto; 28 | padding: 0 1em; 29 | max-width: 60em; 30 | } 31 | 32 | .major-dl > dt { 33 | margin: 0.7em 0 0 0; 34 | border: 1px solid; 35 | border-bottom-color: transparent; 36 | padding: 0 0.5em 0 1em; 37 | } 38 | .major-dl > dd { 39 | margin: 0 0 0.7em 0; 40 | border: 1px solid; 41 | border-top-color: transparent; 42 | padding: 0 0 0 2em; 43 | } 44 | 45 | .directory-listing li { 46 | list-style-type: none; 47 | } 48 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | ShinySDR — Client Tests 23 | 24 | 25 | 26 | 27 | 28 | 29 | 58 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/test/jasmine-glue.js: -------------------------------------------------------------------------------- 1 | // Nearly all of this code is from Jasmine boot.js. Open to suggestions for how to get the effects without copying code from Jasmine. 2 | // 3 | // It has been modified to: 4 | // create a singleton Jasmine environment but not start it onload or define globals 5 | // be compatible with RequireJS 6 | 7 | 'use strict'; 8 | 9 | define(() => { 10 | const jasmine = jasmineRequire.core(jasmineRequire); 11 | jasmineRequire.html(jasmine); 12 | 13 | const env = jasmine.getEnv(); 14 | 15 | const jasmineInterface = jasmineRequire.interface(jasmine, env); 16 | 17 | // --- begin entirely unmodified code --- 18 | const queryString = new jasmine.QueryString({ 19 | getWindowLocation: function() { return window.location; } 20 | }); 21 | 22 | const catchingExceptions = queryString.getParam("catch"); 23 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); 24 | 25 | const throwingExpectationFailures = queryString.getParam("throwFailures"); 26 | env.throwOnExpectationFailure(throwingExpectationFailures); 27 | 28 | const random = queryString.getParam("random"); 29 | env.randomizeTests(random); 30 | 31 | const seed = queryString.getParam("seed"); 32 | if (seed) { 33 | env.seed(seed); 34 | } 35 | 36 | const htmlReporter = new jasmine.HtmlReporter({ 37 | env: env, 38 | onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, 39 | onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); }, 40 | onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); }, 41 | addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, 42 | getContainer: function() { return document.body; }, 43 | createElement: function() { return document.createElement.apply(document, arguments); }, 44 | createTextNode: function() { return document.createTextNode.apply(document, arguments); }, 45 | timer: new jasmine.Timer() 46 | }); 47 | 48 | env.addReporter(jasmineInterface.jsApiReporter); 49 | env.addReporter(htmlReporter); 50 | 51 | const specFilter = new jasmine.HtmlSpecFilter({ 52 | filterString: function() { return queryString.getParam("spec"); } 53 | }); 54 | 55 | env.specFilter = function(spec) { 56 | return specFilter.matches(spec.getFullName()); 57 | }; 58 | 59 | // --- end entirely unmodified code --- 60 | 61 | return Object.freeze({ 62 | ji: jasmineInterface, 63 | start() { 64 | htmlReporter.initialize(); 65 | env.execute(); 66 | } 67 | }); 68 | }); -------------------------------------------------------------------------------- /shinysdr/i/webstatic/test/manual/spectrum-widgets.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | Spectrum widgets tester 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |

Spectrum Test

32 |
33 |
34 | 35 | 36 |

Config

37 |
38 |
39 |
40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/test/t/test_coordination.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | define([ 21 | '/test/jasmine-glue.js', 22 | 'coordination', 23 | 'database', 24 | 'values' 25 | ], ( 26 | import_jasmine, 27 | import_coordination, 28 | import_database, 29 | import_values 30 | ) => { 31 | const {ji: { 32 | describe, 33 | expect, 34 | it, 35 | }} = import_jasmine; 36 | const { 37 | Coordinator, 38 | } = import_coordination; 39 | const { 40 | Table, 41 | } = import_database; 42 | const { 43 | ConstantCell, 44 | makeBlock, 45 | } = import_values; 46 | 47 | describe('Coordinator', () => { 48 | // TODO reduce the need for this stubbing 49 | const stubRadioCell = new ConstantCell(makeBlock({ 50 | source: new ConstantCell(makeBlock({ 51 | freq: new ConstantCell(0), 52 | })), 53 | receivers: new ConstantCell(makeBlock(Object.create(Object.prototype, { 54 | create: {value: function () {}} 55 | }))), 56 | })); 57 | 58 | const stubTable = new Table('stubTable', false); 59 | 60 | it('selected record should follow tuning', function () { 61 | const coordinator = new Coordinator( 62 | 'bogus scheduler', stubTable, stubRadioCell); 63 | const record = new Table('foo', true).add({}); 64 | 65 | expect(coordinator.actions.selectedRecord.get()).toBe(undefined); 66 | coordinator.actions.tune({record: record}); 67 | expect(coordinator.actions.selectedRecord.get()).toBe(record); 68 | coordinator.actions.tune({freq: 1}); // not a record 69 | expect(coordinator.actions.selectedRecord.get()).toBe(record); // unchanged 70 | }); 71 | }); 72 | 73 | return 'ok'; 74 | }); -------------------------------------------------------------------------------- /shinysdr/i/webstatic/test/t/test_network.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | define([ 21 | '/test/jasmine-glue.js', 22 | 'client-configuration-module', 23 | 'events', 24 | 'network', 25 | 'types', 26 | ], ( 27 | import_jasmine, 28 | import_client_configuration, 29 | import_events, 30 | import_network, 31 | import_values 32 | ) => { 33 | const {ji: { 34 | describe, 35 | expect, 36 | it, 37 | }} = import_jasmine; 38 | const { 39 | getSharedTestObjectsURL, 40 | } = import_client_configuration; 41 | const { 42 | Scheduler, 43 | } = import_events; 44 | const { 45 | connect, 46 | } = import_network; 47 | 48 | describe('network', () => { 49 | describe('connect', () => { 50 | it('should successfully connect to the test endpoint', done => { 51 | const scheduler = new Scheduler(); 52 | const rootCell = connect(getSharedTestObjectsURL()); 53 | rootCell.n.listen(scheduler.claim(() => { 54 | expect(rootCell.get()).toBeTruthy(); 55 | done(); 56 | })); 57 | }); 58 | }); 59 | }); 60 | 61 | return 'ok'; 62 | }); -------------------------------------------------------------------------------- /shinysdr/i/webstatic/tools/audio-scope-main.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014, 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | requirejs.config({ 21 | baseUrl: '../client/' 22 | }); 23 | define([ 24 | 'audio/analyser', 25 | 'audio/client-source', 26 | 'audio/util', 27 | 'coordination', 28 | 'events', 29 | 'values', 30 | 'widget', 31 | 'widgets', 32 | 'widgets/scope', 33 | ], ( 34 | import_audio_analyser, 35 | import_audio_client_source, 36 | import_audio_util, 37 | import_coordination, 38 | import_events, 39 | import_values, 40 | import_widget, 41 | widgets, 42 | import_widgets_scope 43 | ) => { 44 | const { 45 | AudioScopeAdapter, 46 | } = import_audio_analyser; 47 | const { 48 | AudioSourceSelector, 49 | } = import_audio_client_source; 50 | const { 51 | audioContextAutoplayHelper, 52 | } = import_audio_util; 53 | const { 54 | ClientStateObject, 55 | } = import_coordination; 56 | const { 57 | Scheduler, 58 | } = import_events; 59 | const { 60 | ConstantCell, 61 | StorageNamespace, 62 | makeBlock, 63 | } = import_values; 64 | const { 65 | Context, 66 | createWidgets, 67 | } = import_widget; 68 | const { 69 | ScopeParameters, 70 | } = import_widgets_scope; 71 | 72 | const scheduler = new Scheduler(); 73 | const audioContext = new AudioContext(); 74 | const storage = sessionStorage; // TODO persistent and namespaced-from-other-pages 75 | 76 | const selector = new AudioSourceSelector(scheduler, audioContext, navigator.mediaDevices, 77 | new StorageNamespace(storage, 'input-selector.')); 78 | const adapter = new AudioScopeAdapter(scheduler, audioContext); 79 | adapter.connectFrom(selector.source); 80 | 81 | const root = new ConstantCell(makeBlock({ 82 | input: new ConstantCell(selector), 83 | scope: adapter.scope, 84 | parameters: new ConstantCell( 85 | new ScopeParameters( 86 | new StorageNamespace(storage, 'scope-parameters.'))), 87 | })); 88 | 89 | const context = new Context({ 90 | widgets: widgets, 91 | // Using sessionStorage because we want default settings and because our storage usage doesn't yet distinguish between different pages. 92 | clientState: new ClientStateObject(sessionStorage, null), 93 | scheduler: scheduler 94 | }); 95 | 96 | createWidgets(root, context, document); 97 | 98 | audioContextAutoplayHelper(audioContext); 99 | }); 100 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/tools/audio-scope.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | Audio Oscilloscope — ShinySDR 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 | 35 | 36 |
39 |
40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/tools/audio-spectrum-main.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014, 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | requirejs.config({ 21 | baseUrl: '../client/' 22 | }); 23 | define([ 24 | 'audio/analyser', 25 | 'audio/client-source', 26 | 'audio/util', 27 | 'coordination', 28 | 'events', 29 | 'values', 30 | 'widget', 31 | 'widgets', 32 | ], ( 33 | import_audio_analyser, 34 | import_audio_client_source, 35 | import_audio_util, 36 | import_coordination, 37 | import_events, 38 | import_values, 39 | import_widget, 40 | widgets 41 | ) => { 42 | const { 43 | AudioAnalyserAdapter, 44 | } = import_audio_analyser; 45 | const { 46 | AudioSourceSelector, 47 | } = import_audio_client_source; 48 | const { 49 | audioContextAutoplayHelper, 50 | } = import_audio_util; 51 | const { 52 | ClientStateObject, 53 | } = import_coordination; 54 | const { 55 | Scheduler, 56 | } = import_events; 57 | const { 58 | ConstantCell, 59 | StorageNamespace, 60 | } = import_values; 61 | const { 62 | Context, 63 | createWidgets, 64 | } = import_widget; 65 | 66 | const scheduler = new Scheduler(); 67 | const audioContext = new AudioContext(); 68 | const storage = sessionStorage; // TODO persistent and namespaced-from-other-pages 69 | 70 | const selector = new AudioSourceSelector(scheduler, audioContext, navigator.mediaDevices, 71 | new StorageNamespace(storage, 'input-selector.')); 72 | const adapter = new AudioAnalyserAdapter(scheduler, audioContext); 73 | adapter.connectFrom(selector.source); 74 | adapter.paused.set(false); 75 | 76 | // kludge: stick extra property on adapter so it gets in the options menu UI. 77 | // TODO: Replace this by adding flexibility to the UI system. 78 | adapter.input = new ConstantCell(selector); 79 | 80 | const root = new ConstantCell(adapter); 81 | 82 | const context = new Context({ 83 | widgets: widgets, 84 | // Using sessionStorage because we want default settings and because our storage usage doesn't yet distinguish between different pages. 85 | clientState: new ClientStateObject(sessionStorage, null), 86 | scheduler: scheduler 87 | }); 88 | 89 | createWidgets(root, context, document); 90 | 91 | audioContextAutoplayHelper(audioContext); 92 | }); 93 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/tools/audio-spectrum.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | Audio Spectrum — ShinySDR 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/tools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | ShinySDR Toolbox 23 | 24 | 25 | 26 | 27 | 28 |

ShinySDR Toolbox

29 | 30 |

This is a collection of tools built on ShinySDR’s components while not being ShinySDR per se. They are usable without a ShinySDR server.

31 | 32 |

All of the current tools visualize audio from any of the audio input devices available to your web browser. (They do not transmit the audio to the server — or so this text claims.)

33 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/tools/worklet-bug-main.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | requirejs.config({ 21 | baseUrl: '../client/' 22 | }); 23 | define([ 24 | 'audio/ws-stream', 25 | 'coordination', 26 | 'events', 27 | 'values', 28 | 'widget', 29 | 'widgets', 30 | ], ( 31 | import_audio_ws_stream, 32 | import_coordination, 33 | import_events, 34 | import_values, 35 | import_widget, 36 | widgets 37 | ) => { 38 | const { 39 | connectAudio, 40 | } = import_audio_ws_stream; 41 | const { 42 | ClientStateObject, 43 | } = import_coordination; 44 | const { 45 | Scheduler, 46 | } = import_events; 47 | const { 48 | ConstantCell, 49 | } = import_values; 50 | const { 51 | Context, 52 | createWidgets, 53 | } = import_widget; 54 | 55 | const scheduler = new Scheduler(); 56 | const storage = sessionStorage; // TODO persistent and namespaced-from-other-pages 57 | 58 | const context = new Context({ 59 | widgets: widgets, 60 | // Using sessionStorage because we want default settings and because our storage usage doesn't yet distinguish between different pages. 61 | clientState: new ClientStateObject(sessionStorage, null), 62 | scheduler: scheduler 63 | }); 64 | 65 | function StubAudioWebSocket() { 66 | this.addEventListener = function(){}; 67 | this.close = function() { console.log('Closed!'); }; 68 | setTimeout(() => { 69 | this.onmessage({data: JSON.stringify({ 70 | type: 'audio_stream_metadata', 71 | signal_type: { 72 | kind: 'MONO', 73 | sample_rate: 44100, 74 | } 75 | })}); 76 | const fakeSamples = new Float32Array(441); 77 | for (let i = 0; i < fakeSamples.length; i++) { 78 | fakeSamples[i] = Math.random() * 0.01; 79 | } 80 | setInterval(() => { 81 | setTimeout(() => { 82 | this.onmessage({data: fakeSamples.buffer}); 83 | }, Math.random() * 200); 84 | }, 10); 85 | }, 0); 86 | } 87 | 88 | let info; 89 | for (let i = 0; i < 1; i++) { 90 | info = connectAudio(scheduler, 'ws://localhost/dummy', storage, StubAudioWebSocket); 91 | } 92 | 93 | createWidgets(new ConstantCell(info), context, document); 94 | 95 | setTimeout(() => { 96 | window.location.reload(); 97 | }, 250); 98 | }); 99 | -------------------------------------------------------------------------------- /shinysdr/i/webstatic/tools/worklet-bug.html: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | AudioWorklet test case 24 | 25 | 26 | 27 | 28 | 29 | 30 |

AudioWorklet test case

31 | 32 | 33 | 34 |
35 | 36 | -------------------------------------------------------------------------------- /shinysdr/math.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013, 2014, 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | """Mathematical algorithms.""" 19 | 20 | from __future__ import absolute_import, division, print_function, unicode_literals 21 | 22 | from math import log10, pi 23 | import time 24 | 25 | 26 | __all__ = [] # appended later 27 | 28 | 29 | def dB(x): 30 | """Convert dB value to multiplicative value.""" 31 | return 10 ** (0.1 * x) 32 | 33 | 34 | __all__.append('dB') 35 | 36 | 37 | def to_dB(x): 38 | """Convert multiplicative value to dB value.""" 39 | return 10 * log10(x) 40 | 41 | 42 | __all__.append('to_dB') 43 | 44 | 45 | def rotator_inc(rate, shift): 46 | """ 47 | Calculation for using gnuradio.blocks.rotator_cc or other interfaces wanting radians/sample input. 48 | 49 | rate: sample rate in Hz 50 | shift: frequency shift in Hz 51 | """ 52 | return (2 * pi) * (shift / rate) 53 | 54 | 55 | __all__.append('rotator_inc') 56 | 57 | 58 | class LazyRateCalculator(object): 59 | # TODO: Not strictly a math thing, should be moved. 60 | """ 61 | Given a monotonically increasing value, allow polling its rate of increase. 62 | """ 63 | def __init__(self, value_getter, min_interval=0.5): 64 | self.__value_getter = value_getter 65 | self.__min_interval = min_interval 66 | 67 | self.__time = time.time() 68 | self.__last_value = value_getter() 69 | self.__last_rate = 0 70 | 71 | def get(self): 72 | cur_wall_time = time.time() 73 | elapsed_wall = cur_wall_time - self.__time 74 | if elapsed_wall > self.__min_interval: 75 | cur_value = self.__value_getter() 76 | delta = cur_value - self.__last_value 77 | self.__time = cur_wall_time 78 | self.__last_value = cur_value 79 | self.__last_rate = round(delta / elapsed_wall, 2) 80 | return self.__last_rate 81 | 82 | 83 | __all__.append('LazyRateCalculator') 84 | -------------------------------------------------------------------------------- /shinysdr/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | # per https://twistedmatrix.com/documents/current/core/howto/plugin.html 2 | from twisted.plugin import pluginPackagePaths 3 | __path__.extend(pluginPackagePaths(__name__)) 4 | __all__ = [] 5 | -------------------------------------------------------------------------------- /shinysdr/plugins/elecraft/client/elecraft.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | define([ 21 | 'widgets', 22 | 'widgets/basic' 23 | ], ( 24 | widgets, 25 | import_widgets_basic 26 | ) => { 27 | const { 28 | Block, 29 | Knob, 30 | Select, 31 | SmallKnob, 32 | } = import_widgets_basic; 33 | 34 | const exports = {}; 35 | 36 | function ElecraftRadio(config) { 37 | Block.call(this, config, (block, addWidget, ignore, setInsertion, setToDetails, getAppend) => { 38 | // TODO: Shouldn't have to repeat label strings that the server already provides, but usages of addWidget are a hairy thing. 39 | 40 | addWidget('MC', SmallKnob, 'Memory'); 41 | 42 | // KX3 first knob 43 | // AF is in receiver 44 | addWidget('SQ', null, 'Squelch'); 45 | addWidget('ML', null, 'Monitor Level'); 46 | // second knob 47 | // third knob 48 | addWidget('MG', null, 'Mic Gain'); 49 | addWidget('PC', null, 'TX Power'); 50 | 51 | function header(text) { 52 | const element = getAppend().appendChild(document.createElement('div')); 53 | element.className = 'panel frame-controls'; 54 | element.textContent = text; 55 | } 56 | // TODO: On KX3 these should be called A and B 57 | header('Main VFO'); 58 | addWidget('rx_main'); 59 | header('Sub VFO'); 60 | addWidget('rx_sub'); 61 | 62 | setToDetails(); 63 | }); 64 | } 65 | 66 | function ElecraftReceiver(config) { 67 | Block.call(this, config, (block, addWidget, ignore, setInsertion, setToDetails, getAppend) => { 68 | addWidget('freq', Knob, ''); 69 | addWidget('MD', Select, 'Mode'); 70 | addWidget('AG', null, 'AF Gain'); 71 | addWidget('LK', null, 'Lock'); 72 | addWidget('PA', null, 'RX Preamp'); 73 | 74 | setToDetails(); 75 | 76 | ignore('BN'); 77 | }); 78 | } 79 | 80 | // TODO: Better widget-plugin system so we're not modifying should-be-static tables 81 | widgets['interface:shinysdr.plugins.elecraft.IElecraftRadio'] = ElecraftRadio; 82 | widgets['interface:shinysdr.plugins.elecraft.IElecraftReceiver'] = ElecraftReceiver; 83 | 84 | return Object.freeze(exports); 85 | }); 86 | -------------------------------------------------------------------------------- /shinysdr/plugins/hamlib/client/hamlib.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | define([ 21 | 'widgets', 22 | 'widgets/basic', 23 | ], ( 24 | widgets, 25 | import_widgets_basic 26 | ) => { 27 | const { 28 | Banner, 29 | Block, 30 | Meter, 31 | Radio, 32 | } = import_widgets_basic; 33 | 34 | const exports = {}; 35 | 36 | function ExtRig(config) { 37 | Block.call(this, config, (block, addWidget, ignore, setInsertion, setToDetails, getAppend) => { 38 | ignore('freq'); // is merged into vfo 39 | if ('Mode' in block) { 40 | addWidget('Mode', Radio, 'Mode'); 41 | } 42 | addWidget('errors', Banner); 43 | if ('STRENGTH level' in block) { 44 | addWidget('STRENGTH level', Meter, 'S'); 45 | ignore('RAWSTR level'); 46 | } 47 | 48 | setToDetails(); 49 | }); 50 | } 51 | 52 | // TODO: Better widget-plugin system so we're not modifying should-be-static tables 53 | widgets['interface:shinysdr.plugins.hamlib.IRig'] = ExtRig; 54 | 55 | return Object.freeze(exports); 56 | }); 57 | -------------------------------------------------------------------------------- /shinysdr/plugins/mode_s/client/aircraft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 16 | 17 | 37 | 38 | -------------------------------------------------------------------------------- /shinysdr/plugins/mode_s/client/mode_s.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014, 2015, 2016, 2017 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | define([ 21 | 'require', 22 | 'map/map-core', 23 | 'widgets', 24 | 'widgets/basic', 25 | ], ( 26 | require, 27 | import_map_core, 28 | widgets, 29 | import_widgets_basic 30 | ) => { 31 | const { 32 | register, 33 | renderTrackFeature, 34 | } = import_map_core; 35 | const { 36 | Block, 37 | } = import_widgets_basic; 38 | 39 | const exports = {}; 40 | 41 | function AircraftWidget(config) { 42 | Block.call(this, config, function (block, addWidget, ignore, setInsertion, setToDetails, getAppend) { 43 | addWidget('track', widgets.TrackWidget); 44 | }, false); 45 | } 46 | 47 | // TODO: Better widget-plugin system so we're not modifying should-be-static tables 48 | widgets['interface:shinysdr.plugins.mode_s.IAircraft'] = AircraftWidget; 49 | 50 | function addAircraftMapLayer(mapPluginConfig) { 51 | mapPluginConfig.addLayer('Aircraft', { 52 | featuresCell: mapPluginConfig.index.implementing('shinysdr.plugins.mode_s.IAircraft'), 53 | featureRenderer: function renderAircraft(aircraft, dirty) { 54 | const trackCell = aircraft.track; 55 | const callsign = aircraft.call.depend(dirty); 56 | const ident = aircraft.ident.depend(dirty); 57 | const altitude = trackCell.depend(dirty).altitude.value; 58 | const labelParts = []; 59 | if (callsign !== null) { 60 | labelParts.push(callsign.replace(/^ | $/g, '')); 61 | } 62 | if (ident !== null) { 63 | labelParts.push(ident); 64 | } 65 | if (altitude !== null) { 66 | labelParts.push(altitude.toFixed(0) + ' m'); 67 | } 68 | const f = renderTrackFeature(dirty, trackCell, 69 | labelParts.join(' • ')); 70 | f.iconURL = require.toUrl('./aircraft.svg'); 71 | return f; 72 | } 73 | }); 74 | } 75 | 76 | register(addAircraftMapLayer); 77 | 78 | return Object.freeze(exports); 79 | }); 80 | -------------------------------------------------------------------------------- /shinysdr/plugins/rebooter.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016, 2017, 2018 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | import os 21 | import sys 22 | 23 | from zope.interface import implementer 24 | 25 | from shinysdr.devices import Device, IComponent 26 | from shinysdr.values import ExportedState, command 27 | 28 | 29 | __all__ = ['Rebooter'] 30 | 31 | 32 | def Rebooter(reactor): 33 | return Device(components={'rebooter': _RebooterComponent(reactor)}) 34 | 35 | 36 | @implementer(IComponent) 37 | class _RebooterComponent(ExportedState): 38 | def __init__(self, reactor): 39 | self.__reactor = reactor 40 | 41 | def close(self): 42 | """implements IComponent""" 43 | 44 | def attach_context(self, device_context): 45 | """implements IComponent""" 46 | 47 | @command(label='Restart server') 48 | def reboot(self): 49 | # Note that this will immediately kill us and so we will never ack the client invocation -- which we're doing as a deliberate indication of our temporary death. 50 | # TODO: Do better preservation of interpreter options, etc. 51 | os.execlp(sys.executable or 'python', 'python', 52 | '-m', 'shinysdr.main', *sys.argv[1:]) 53 | 54 | @command(label='Kill server') 55 | def kill(self): 56 | # pylint: disable=no-member 57 | self.__reactor.stop() 58 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_basic_demod.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from shinysdr.testutil import DemodulatorTestCase 21 | 22 | 23 | class TestIQ(DemodulatorTestCase): 24 | def setUp(self): 25 | self.setUpFor(mode='IQ') 26 | 27 | 28 | class TestAM(DemodulatorTestCase): 29 | def setUp(self): 30 | self.setUpFor(mode='AM') 31 | 32 | 33 | class TestUnselectiveAM(DemodulatorTestCase): 34 | def setUp(self): 35 | self.setUpFor(mode='AM-unsel') 36 | 37 | 38 | class TestNFM(DemodulatorTestCase): 39 | def setUp(self): 40 | self.setUpFor(mode='NFM') 41 | 42 | 43 | class TestWFM(DemodulatorTestCase): 44 | def setUp(self): 45 | self.setUpFor(mode='WFM') 46 | 47 | 48 | class TestLSB(DemodulatorTestCase): 49 | def setUp(self): 50 | self.setUpFor(mode='LSB') 51 | 52 | 53 | class TestUSB(DemodulatorTestCase): 54 | def setUp(self): 55 | self.setUpFor(mode='USB') 56 | 57 | 58 | class TestCW(DemodulatorTestCase): 59 | def setUp(self): 60 | self.setUpFor(mode='CW') 61 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_controller.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2016, 2018 Kevin Reid and the ShinySDR contributors 3 | # 4 | # This file is part of ShinySDR. 5 | # 6 | # ShinySDR is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # ShinySDR is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with ShinySDR. If not, see . 18 | 19 | from __future__ import absolute_import, division, print_function, unicode_literals 20 | 21 | from twisted.trial import unittest 22 | from twisted.internet import defer 23 | from twisted.internet import reactor as the_reactor 24 | 25 | from shinysdr.plugins.controller import Controller, Command, Selector 26 | from shinysdr.testutil import StringTransportEndpoint, state_smoke_test 27 | from shinysdr.types import EnumT 28 | 29 | 30 | class TestController(unittest.TestCase): 31 | """ 32 | Also contains generic proxy tests. 33 | """ 34 | timeout = 5 35 | 36 | def setUp(self): 37 | self.endpoint = StringTransportEndpoint() 38 | self.t = self.endpoint.string_transport 39 | self.device = Controller( 40 | reactor=the_reactor, 41 | endpoint=self.endpoint, 42 | elements=[ 43 | Command('cmd_name', 'cmd_text'), 44 | Command('unicode_cmd', u'façade'), 45 | Selector('enum_name', EnumT({u'enum_text1': u'enum_label1', u'enum_text2': u'enum_label2'}, strict=False)) 46 | ], 47 | encoding='UTF-8') 48 | self.proxy = self.device.get_components_dict()['controller'] 49 | 50 | @defer.inlineCallbacks 51 | def tearDown(self): 52 | yield self.device.close() 53 | self.assertTrue(self.t.disconnecting) 54 | 55 | def test_state_smoke(self): 56 | state_smoke_test(self.device) 57 | 58 | def test_send_command(self): 59 | self.proxy.state()['cmd_name'].set(True) # TODO command-cell kludge 60 | self.assertEqual(b'cmd_text', self.t.value()) 61 | 62 | def test_send_enum(self): 63 | self.proxy.state()['enum_name'].set('enum_text1') 64 | self.t.clear() 65 | self.proxy.state()['enum_name'].set('enum_text2') 66 | self.assertEqual(b'enum_text2', self.t.value()) 67 | 68 | def test_encode_command(self): 69 | self.proxy.state()['unicode_cmd'].set(True) # TODO command-cell kludge 70 | self.assertEqual(u'façade'.encode('UTF-8'), self.t.value()) 71 | 72 | def test_encode_enum(self): 73 | self.proxy.state()['enum_name'].set(u'façade') 74 | self.assertEqual(u'façade'.encode('UTF-8'), self.t.value()) 75 | 76 | # TODO: Test reconnection behavior. 77 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_dsd.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from shinysdr.testutil import DemodulatorTestCase 21 | 22 | 23 | class TestDSD(DemodulatorTestCase): 24 | def setUp(self): 25 | self.setUpFor(mode='DSD', skip_if_unavailable=True) 26 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_elecraft.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.test.proto_helpers import StringTransport 21 | from twisted.trial import unittest 22 | from twisted.internet.task import Clock 23 | 24 | from shinysdr.plugins.elecraft import _ElecraftClientProtocol 25 | from shinysdr.testutil import state_smoke_test 26 | 27 | 28 | class TestElecraftProtocol(unittest.TestCase): 29 | """Test _ElecraftClientProtocol and _ElecraftRadio. 30 | 31 | This test uses those implementation classes rather than the public interface because the public interface is hardcoded to attempt to open a serial device.""" 32 | 33 | def setUp(self): 34 | self.clock = Clock() 35 | self.tr = StringTransport() 36 | self.protocol = _ElecraftClientProtocol(reactor=self.clock) 37 | self.proxy = self.protocol._proxy() 38 | self.protocol.makeConnection(self.tr) 39 | self.protocol.connectionMade() 40 | 41 | def test_state_smoke(self): 42 | state_smoke_test(self.proxy) 43 | 44 | def test_initial_send(self): 45 | self.assertIn(b'AI2;', self.tr.value()) 46 | self.assertIn(b'K31;', self.tr.value()) 47 | self.assertIn(b'IF;', self.tr.value()) 48 | 49 | def test_simple_receive(self): 50 | self.protocol.dataReceived(b'FA00000000012;') 51 | self.assertEqual(12.0, self.proxy.get_rx_main().state()['freq'].get()) 52 | 53 | def test_continues_after_bad_data(self): 54 | self.protocol.dataReceived(b'\x00\x00;;FA00000000012;') 55 | self.assertEqual(12.0, self.proxy.get_rx_main().state()['freq'].get()) 56 | 57 | def test_not_too_much_polling(self): 58 | self.tr.clear() 59 | self.assertEqual(b'', self.tr.value()) 60 | self.clock.pump([0.01] * 150) 61 | self.assertEqual(b'FA;', self.tr.value()) 62 | self.clock.pump([0.01] * 500) 63 | self.assertEqual(b'FA;FA;FA;FA;FA;FA;', self.tr.value()) 64 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_hamlib.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.trial import unittest 21 | from twisted.internet import defer, reactor 22 | 23 | from shinysdr.plugins.hamlib import connect_to_rig, connect_to_rotator 24 | from shinysdr.testutil import state_smoke_test 25 | 26 | 27 | class TestHamlibRig(unittest.TestCase): 28 | """ 29 | Also contains generic proxy tests. 30 | """ 31 | timeout = 5 32 | __rig = None 33 | 34 | def setUp(self): 35 | d = connect_to_rig(reactor, options=['-m', '1']) 36 | 37 | def on_connect(rig_device): 38 | self.__rig = rig_device.get_components_dict()['rig'] 39 | 40 | # pylint: disable=no-member 41 | d.addCallback(on_connect) 42 | return d 43 | 44 | def tearDown(self): 45 | return self.__rig.close() 46 | 47 | def test_noop(self): 48 | """basic connect and disconnect, check is clean""" 49 | pass 50 | 51 | @defer.inlineCallbacks 52 | def test_state_smoke(self): 53 | state_smoke_test(self.__rig) 54 | yield self.__rig.sync() 55 | state_smoke_test(self.__rig) 56 | 57 | @defer.inlineCallbacks 58 | def test_getter(self): 59 | yield self.__rig.sync() 60 | self.assertEqual(self.__rig.state()['freq'].get(), 145e6) 61 | 62 | @defer.inlineCallbacks 63 | def test_setter(self): 64 | yield self.__rig.sync() 65 | self.__rig.state()['freq'].set(123e6) 66 | yield self.__rig.sync() 67 | self.assertEqual(self.__rig.state()['freq'].get(), 123e6) 68 | 69 | @defer.inlineCallbacks 70 | def test_sync(self): 71 | yield self.__rig.sync() 72 | yield self.__rig.sync() 73 | 74 | 75 | class TestHamlibRotator(unittest.TestCase): 76 | timeout = 5 77 | __rotator = None 78 | 79 | def setUp(self): 80 | d = connect_to_rotator(reactor, options=['-m', '1']) 81 | 82 | def on_connect(rotator_device): 83 | self.__rotator = rotator_device.get_components_dict()['rotator'] 84 | 85 | # pylint: disable=no-member 86 | d.addCallback(on_connect) 87 | return d 88 | 89 | def tearDown(self): 90 | return self.__rotator.close() 91 | 92 | def test_noop(self): 93 | """basic connect and disconnect, check is clean""" 94 | pass 95 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_mode_s.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from shinysdr.testutil import DemodulatorTestCase 21 | 22 | 23 | class TestModeS(DemodulatorTestCase): 24 | def setUp(self): 25 | self.setUpFor(mode='MODE-S', skip_if_unavailable=True) 26 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_multimon.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, 2018 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from shinysdr.plugins.multimon import FMAPRSDemodulator 21 | from shinysdr.testutil import DemodulatorTestCase 22 | 23 | 24 | class TestFMAPRSDemodulator(DemodulatorTestCase): 25 | def setUp(self): 26 | self.setUpFor(mode='APRS', skip_if_unavailable=True, demod_class=FMAPRSDemodulator) 27 | 28 | def tearDown(self): 29 | self.demodulator._close() # TODO temporary kludge!!! Clean up in a way that actually works in non-tests! 30 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_psk31.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, 2015, 2016, 2017 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from shinysdr.plugins.psk31 import PSK31Demodulator 21 | from shinysdr.testutil import DemodulatorTestCase 22 | 23 | 24 | class TestPSK31Demodulator(DemodulatorTestCase): 25 | def setUp(self): 26 | self.setUpFor(mode='PSK31', skip_if_unavailable=True, demod_class=PSK31Demodulator) 27 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_rtl_433.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, 2015, 2016, 2017, 2018 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | import six 21 | 22 | from twisted.trial import unittest 23 | 24 | from shinysdr.plugins.rtl_433 import RTL433Demodulator, RTL433MessageWrapper, RTL433MsgGroup, RTL433ProcessProtocol 25 | from shinysdr.testutil import DemodulatorTestCase, LogTester 26 | 27 | 28 | class TestRTL433Demodulator(DemodulatorTestCase): 29 | def setUp(self): 30 | self.setUpFor(mode='rtl_433', skip_if_unavailable=True, demod_class=RTL433Demodulator) 31 | 32 | def tearDown(self): 33 | self.demodulator._close() # TODO temporary kludge!!! Clean up in a way that actually works in non-tests! 34 | 35 | 36 | class TestRTL433Protocol(unittest.TestCase): 37 | """Check behavior of protocol object against fixed test data.""" 38 | timeout = 5 39 | 40 | def setUp(self): 41 | self.log_tester = LogTester() 42 | self.received = [] 43 | self.protocol = RTL433ProcessProtocol(target=self.received.append, log=self.log_tester.log) 44 | 45 | def test_success(self): 46 | self.protocol.outReceived(b'{"foo":"bar"}\n') 47 | if six.PY2: 48 | self.log_tester.check(dict(text="rtl_433 message: {u'foo': u'bar'}")) 49 | else: 50 | self.log_tester.check(dict(text="rtl_433 message: {'foo': 'bar'}")) 51 | self.assertEqual(len(self.received), 1) 52 | 53 | def test_not_json(self): 54 | self.protocol.outReceived(b'foo\n') 55 | self.log_tester.check(dict(text="bad JSON from rtl_433: 'foo'")) 56 | self.assertEqual(self.received, []) 57 | 58 | 59 | class TestMessageWrapperAndGroup(unittest.TestCase): 60 | def test_id_calculation_example(self): 61 | m = RTL433MessageWrapper( 62 | {'temperature_C': 30.4, 'model': 'LaCrosse-TX', 'id': 2, 'time': '@616.798279s'}, 63 | 1000) 64 | self.assertEqual(m.get_object_id(), '2-LaCrosse-TX') 65 | 66 | def test_ids_and_metadata_are_hidden(self): 67 | m = RTL433MessageWrapper( 68 | {'temperature_C': 30.4, 'model': 'LaCrosse-TX', 'id': 2, 'time': '@616.798279s'}, 69 | 1000) 70 | g = RTL433MsgGroup(m.get_object_id()) 71 | g.receive(m) 72 | # 'model', 'id', and 'time' do not become cells 73 | self.assertEqual(set(g.state().keys()), {'temperature_C', 'last_heard_time'}) 74 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_rtty.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, 2015, 2016 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.trial import unittest 21 | 22 | import numpy 23 | import numpy.testing 24 | 25 | from shinysdr.plugins import rtty 26 | from shinysdr.testutil import DemodulatorTestCase 27 | 28 | 29 | class TestRTTY(DemodulatorTestCase): 30 | def setUp(self): 31 | self.setUpFor(mode='RTTY', skip_if_unavailable=True) 32 | 33 | 34 | class TestRTTYEncoder(unittest.TestCase): 35 | def setUp(self): 36 | # self.encoder = rtty.RTTYEncoder() 37 | pass 38 | 39 | def __wrap(self, data_bits): 40 | out = [0, 0] 41 | for b in data_bits: 42 | out.append(b) 43 | out.append(b) 44 | out.append(1) 45 | out.append(1) 46 | out.append(1) 47 | return out 48 | 49 | def __run_encoder(self, input_chars): 50 | return rtty._encode_rtty_alloc( 51 | numpy.array([ord(x) for x in input_chars], dtype=numpy.uint8)) 52 | 53 | def test_basic(self): 54 | # pylint: disable=no-member 55 | # (pylint glitch) 56 | 57 | # TODO wrong assert 58 | numpy.testing.assert_array_equal(self.__run_encoder('QE'), numpy.array( 59 | self.__wrap([1, 1, 1, 0, 1]) + 60 | self.__wrap([1, 0, 0, 0, 0]), dtype=numpy.float32)) 61 | -------------------------------------------------------------------------------- /shinysdr/plugins/test_simulate.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from shinysdr.plugins.simulate import SimulatedDevice, SimulatedDeviceForTest 21 | from shinysdr.testutil import DeviceTestCase 22 | 23 | 24 | class TestSimulatedDevice(DeviceTestCase): 25 | def setUp(self): 26 | super(TestSimulatedDevice, self).setUpFor( 27 | device=SimulatedDevice()) 28 | 29 | # Test methods provided by DeviceTestCase 30 | 31 | 32 | class TestSimulatedDeviceForTest(DeviceTestCase): 33 | def setUp(self): 34 | super(TestSimulatedDeviceForTest, self).setUpFor( 35 | device=SimulatedDeviceForTest()) 36 | 37 | # Test methods provided by DeviceTestCase 38 | -------------------------------------------------------------------------------- /shinysdr/plugins/wspr/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.python.util import sibpath 21 | from twisted.web import static 22 | 23 | from shinysdr.interfaces import ModeDef, ClientResourceDef 24 | 25 | from .demodulator import WSPRDemodulator, find_wsprd 26 | 27 | plugin_mode = ModeDef(mode='WSPR', 28 | info='WSPR', 29 | demod_class=WSPRDemodulator, 30 | unavailability=None if find_wsprd() else 'wsprd not found.') 31 | 32 | plugin_client = ClientResourceDef( 33 | key=__name__, 34 | resource=static.File(sibpath(__file__, 'client')), 35 | load_js_path='wspr.js') 36 | 37 | __all__ = [] 38 | -------------------------------------------------------------------------------- /shinysdr/plugins/wspr/client/w.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /shinysdr/plugins/wspr/client/wspr.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014, 2015, 2016, 2017 Kevin Reid and the ShinySDR contributors 2 | // 3 | // This file is part of ShinySDR. 4 | // 5 | // ShinySDR is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // ShinySDR is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with ShinySDR. If not, see . 17 | 18 | 'use strict'; 19 | 20 | define([ 21 | 'require', 22 | 'map/map-core', 23 | 'widgets', 24 | ], ( 25 | require, 26 | import_map_core, 27 | widgets 28 | ) => { 29 | const { 30 | register, 31 | renderTrackFeature, 32 | } = import_map_core; 33 | const { 34 | Block, 35 | TrackWidget, 36 | } = widgets; 37 | 38 | const exports = {}; 39 | 40 | function WSPRWidget(config) { 41 | Block.call(this, config, function (block, addWidget, ignore, setInsertion, setToDetails, getAppend) { 42 | addWidget('track', TrackWidget); 43 | }, false); 44 | } 45 | 46 | // TODO: Better widget-plugin system so we're not modifying should-be-static tables 47 | widgets['interface:shinysdr.plugins.mode_s.IWSPRStation'] = WSPRWidget; 48 | 49 | function addWSPRMapLayer(mapPluginConfig) { 50 | mapPluginConfig.addLayer('WSPR', { 51 | featuresCell: mapPluginConfig.index.implementing('shinysdr.plugins.wspr.telemetry.IWSPRStation'), 52 | featureRenderer: function renderWSPR(station, dirty) { 53 | let callsign = station.call.depend(dirty); 54 | if (callsign === null) { 55 | callsign = '?'; 56 | } 57 | 58 | const f = renderTrackFeature(dirty, station.track, callsign); 59 | f.iconURL = require.toUrl('./w.svg'); 60 | return f; 61 | } 62 | }); 63 | } 64 | 65 | register(addWSPRMapLayer); 66 | 67 | return Object.freeze(exports); 68 | }); 69 | -------------------------------------------------------------------------------- /shinysdr/plugins/wspr/interfaces.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from zope.interface import Interface 21 | 22 | 23 | class IWAVIntervalListener(Interface): 24 | def fileClosed(filename): 25 | """A recording just finished and the file is closed.""" 26 | 27 | def fileOpened(filename): 28 | """A file was just opened and recording has started.""" 29 | 30 | def filename(start_time): 31 | """Return what the recording should be named. 32 | 33 | `start_time` is in seconds since epoch. 34 | """ 35 | -------------------------------------------------------------------------------- /shinysdr/plugins/wspr/test_telemetry.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.trial import unittest 21 | 22 | from zope.interface.verify import verifyObject 23 | 24 | from shinysdr.telemetry import ITelemetryMessage, ITelemetryObject 25 | from shinysdr.plugins.wspr.telemetry import WSPRSpot, WSPRStation, IWSPRStation, grid_to_lat_long 26 | 27 | 28 | class TestWSPRSpot(unittest.TestCase): 29 | def test_interface(self): 30 | spot = WSPRSpot(None, None, None, None, None, None, None, None) 31 | verifyObject(ITelemetryMessage, spot) 32 | 33 | 34 | class TestWSPRStation(unittest.TestCase): 35 | def setUp(self): 36 | self.station = WSPRStation(None) 37 | 38 | def test_interface(self): 39 | verifyObject(ITelemetryObject, self.station) 40 | verifyObject(IWSPRStation, self.station) 41 | 42 | def test_expiry(self): 43 | """Stations expire 30 minutes after the last spot.""" 44 | t = 13987317.3 45 | spot = WSPRSpot(t, None, None, None, None, None, None, None) 46 | self.station.receive(spot) 47 | expiry = self.station.get_object_expiry() 48 | self.assertIsInstance(expiry, float) 49 | self.assertEqual(expiry, t + 30 * 60) 50 | 51 | t += 600 52 | spot = WSPRSpot(t, None, None, None, None, None, None, None) 53 | self.station.receive(spot) 54 | expiry = self.station.get_object_expiry() 55 | self.assertEqual(expiry, t + 30 * 60) 56 | 57 | 58 | class TestGridToLatLon(unittest.TestCase): 59 | def assertLatLongAlmostEqual(self, grid, b): 60 | lat_a, lon_a = grid_to_lat_long(grid) 61 | lat_b, lon_b = b 62 | self.assertAlmostEqual(lat_a, lat_b) 63 | self.assertAlmostEqual(lon_a, lon_b) 64 | 65 | def test_valid_squares(self): 66 | self.assertLatLongAlmostEqual('AA00', (-89.5, -179)) 67 | self.assertLatLongAlmostEqual('RR99', (89.5, 179)) 68 | self.assertLatLongAlmostEqual('EN82', (42.5, -83)) 69 | 70 | def test_valid_subsquares(self): 71 | self.assertLatLongAlmostEqual('AA00aa', (-90 + 2.5 / 60 / 2, -180 + 5 / 60 / 2)) 72 | self.assertLatLongAlmostEqual('RR99xx', (90 - 2.5 / 60 / 2, 180 - 5 / 60 / 2)) 73 | self.assertLatLongAlmostEqual('EN82fo', (42 + (14.5 * 2.5) / 60, -84 + (5.5 * 5) / 60)) 74 | 75 | def test_case_insensitive(self): 76 | self.assertLatLongAlmostEqual('en82FO', (42 + (14.5 * 2.5) / 60, -84 + (5.5 * 5) / 60)) 77 | 78 | def test_invalid_grids(self): 79 | self.assertRaises(ValueError, grid_to_lat_long, '1') 80 | self.assertRaises(ValueError, grid_to_lat_long, 'AA0') 81 | self.assertRaises(ValueError, grid_to_lat_long, 'AA00a') 82 | self.assertRaises(ValueError, grid_to_lat_long, 'AA0z') 83 | self.assertRaises(ValueError, grid_to_lat_long, 'AA00aaa') 84 | -------------------------------------------------------------------------------- /shinysdr/plugins/wspr/wspr.csv: -------------------------------------------------------------------------------- 1 | Mode,Frequency,Name 2 | WSPR,1.8381,WSPR 3 | WSPR,3.5941,WSPR 4 | WSPR,5.2887,WSPR 5 | WSPR,7.0401,WSPR 6 | WSPR,10.1402,WSPR 7 | WSPR,14.0971,WSPR 8 | WSPR,18.1061,WSPR 9 | WSPR,21.0961,WSPR 10 | WSPR,24.9261,WSPR 11 | WSPR,28.1261,WSPR 12 | WSPR,50.2945,WSPR 13 | WSPR,144.49,WSPR 14 | -------------------------------------------------------------------------------- /shinysdr/signals.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014, 2016 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from zope.interface import implementer 21 | 22 | from gnuradio import gr 23 | 24 | from shinysdr.types import EnumT, IJsonSerializable 25 | 26 | 27 | # TODO: It is unclear whether this module is a sensible division of the program. Think about it some more. 28 | 29 | 30 | __all__ = [] # appended later 31 | 32 | 33 | _kind_t = EnumT({k: k for k in { 34 | 'NONE', # no port at all, non-sample-rated 35 | 'IQ', # gr_complex 36 | 'USB', # gr_complex, zero Q 37 | 'LSB', # gr_complex, zero Q 38 | 'MONO', # float 39 | 'STEREO', # vector of 2 float 40 | }}) 41 | 42 | 43 | @implementer(IJsonSerializable) 44 | class SignalType(object): 45 | def __init__(self, kind, sample_rate=0.0): 46 | kind = _kind_t(kind) 47 | sample_rate = float(sample_rate) 48 | if kind == 'NONE': 49 | if sample_rate != 0: 50 | raise ValueError('Sample rate must be zero for kind {!r}'.format(kind)) 51 | else: 52 | if not sample_rate > 0: 53 | raise ValueError('Sample rate must be positive, not {}'.format(sample_rate)) 54 | 55 | self.__kind = kind 56 | self.__sample_rate = sample_rate 57 | 58 | def __eq__(self, other): 59 | # pylint: disable=unidiomatic-typecheck 60 | if type(self) != type(other): 61 | return False 62 | else: 63 | return ( 64 | self.__kind == other.__kind and 65 | self.__sample_rate == other.__sample_rate) 66 | 67 | def __hash__(self): 68 | return hash(self.__kind) ^ hash(self.__sample_rate) 69 | 70 | def get_sample_rate(self): 71 | """Sample rate in samples per second. 72 | 73 | Zero if the type has no sample rate. 74 | """ 75 | return self.__sample_rate 76 | 77 | def get_kind(self): 78 | # TODO will probably want to change this 79 | """ 80 | One of 'NONE', 'IQ', 'USB', 'LSB', 'MONO', or 'STEREO'. 81 | 82 | Note that due to the current implementation, USB and LSB are complex with a zero Q component. 83 | """ 84 | return self.__kind 85 | 86 | def get_itemsize(self): 87 | if self.__kind == 'NONE': 88 | return 0 89 | elif self.__kind == 'MONO': 90 | return gr.sizeof_float 91 | elif self.__kind == 'STEREO': 92 | return gr.sizeof_float * 2 93 | else: 94 | return gr.sizeof_gr_complex 95 | 96 | def is_analytic(self): 97 | # TODO: This is misnamed 98 | """Regardless of the signal being represented as gr_complex, does it have a two-sided spectrum?""" 99 | return self.__kind == 'IQ' 100 | 101 | def compatible_items(self, other): 102 | assert isinstance(other, SignalType) 103 | # there could be same-size incompatible items but there aren't yet 104 | # whether IQ and STEREO are incompatible is arguable 105 | return self.get_itemsize() == other.get_itemsize() 106 | 107 | def to_json(self): 108 | return { 109 | 'type': 'SignalType', 110 | 'kind': self.get_kind(), 111 | 'sample_rate': self.get_sample_rate(), 112 | } 113 | 114 | 115 | __all__.append('SignalType') 116 | 117 | 118 | no_signal = SignalType(kind='NONE', sample_rate=0.0) 119 | 120 | 121 | __all__.append('no_signal') 122 | -------------------------------------------------------------------------------- /shinysdr/test_grc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.trial import unittest 21 | 22 | from shinysdr import grc 23 | from shinysdr.plugins.basic_demod import AMModulator, NFMDemodulator, UnselectiveAMDemodulator 24 | 25 | 26 | class TestDemodulatorAdapter(unittest.TestCase): 27 | def test_stereo_resample(self): 28 | # AM-unsel is an example of a stereo demodulator 29 | adapter = grc.DemodulatorAdapter(mode='AM-unsel', input_rate=100000, output_rate=22050) 30 | self.assertIsInstance(adapter.get_demodulator(), UnselectiveAMDemodulator) 31 | self.assertNotEqual(adapter.get_demodulator().get_output_type().get_sample_rate(), 22050) 32 | 33 | def test_mono_resample(self): 34 | # NFM is an example of a mono demodulator 35 | adapter = grc.DemodulatorAdapter(mode='NFM', input_rate=100000, output_rate=22050) 36 | self.assertIsInstance(adapter.get_demodulator(), NFMDemodulator) 37 | self.assertNotEqual(adapter.get_demodulator().get_output_type().get_sample_rate(), 22050) 38 | 39 | def test_stereo_direct(self): 40 | adapter = grc.DemodulatorAdapter(mode='AM-unsel', input_rate=100000, output_rate=10000) 41 | self.assertIsInstance(adapter.get_demodulator(), UnselectiveAMDemodulator) 42 | self.assertEqual(adapter.get_demodulator().get_output_type().get_sample_rate(), 10000) 43 | 44 | def test_mono_direct(self): 45 | adapter = grc.DemodulatorAdapter(mode='NFM', input_rate=100000, output_rate=10000) 46 | self.assertIsInstance(adapter.get_demodulator(), NFMDemodulator) 47 | self.assertEqual(adapter.get_demodulator().get_output_type().get_sample_rate(), 10000) 48 | 49 | 50 | class TestModulatorAdapter(unittest.TestCase): 51 | def test_direct(self): 52 | adapter = grc.ModulatorAdapter(mode='AM', input_rate=10000, output_rate=10000) 53 | self.assertIsInstance(adapter.get_modulator(), AMModulator) 54 | self.assertEqual(adapter.get_modulator().get_input_type().get_sample_rate(), 10000) 55 | self.assertEqual(adapter.get_modulator().get_output_type().get_sample_rate(), 10000) 56 | 57 | def test_resample(self): 58 | adapter = grc.ModulatorAdapter(mode='AM', input_rate=20000, output_rate=20000) 59 | self.assertIsInstance(adapter.get_modulator(), AMModulator) 60 | self.assertNotEqual(adapter.get_modulator().get_input_type().get_sample_rate(), 20000) 61 | self.assertNotEqual(adapter.get_modulator().get_output_type().get_sample_rate(), 20000) 62 | -------------------------------------------------------------------------------- /shinysdr/test_manually/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpreid/shinysdr/25022d36903ff67e036e82a22b6555a12a4d8e8a/shinysdr/test_manually/__init__.py -------------------------------------------------------------------------------- /shinysdr/test_manually/aprs_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2014, 2016 Kevin Reid and the ShinySDR contributors 4 | # 5 | # This file is part of ShinySDR. 6 | # 7 | # ShinySDR is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # ShinySDR is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with ShinySDR. If not, see . 19 | 20 | """ 21 | Test for APRS parser. Accepts lines and prints the parsed form. 22 | """ 23 | 24 | from __future__ import absolute_import, division, print_function, unicode_literals 25 | 26 | import string 27 | import sys 28 | import time 29 | 30 | from shinysdr.plugins import aprs 31 | 32 | 33 | if __name__ == '__main__': 34 | for line in sys.stdin: 35 | print(string.rstrip(line, '\n')) 36 | parsed = aprs.parse_tnc2(line, time.time()) 37 | for error in parsed.errors: 38 | print('--!--', error) 39 | for fact in parsed.facts: 40 | print(' ', fact) 41 | print() 42 | -------------------------------------------------------------------------------- /shinysdr/test_manually/channel_filter_benchmark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2013, 2014, 2015, 2016 Kevin Reid and the ShinySDR contributors 4 | # 5 | # This file is part of ShinySDR. 6 | # 7 | # ShinySDR is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # ShinySDR is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with ShinySDR. If not, see . 19 | 20 | """ 21 | Benchmark for MultistageChannelFilter. 22 | """ 23 | 24 | from __future__ import absolute_import, division, print_function, unicode_literals 25 | 26 | import time 27 | 28 | from gnuradio import blocks 29 | from gnuradio import gr 30 | 31 | from shinysdr.filters import MultistageChannelFilter 32 | 33 | 34 | def test_one_filter(**kwargs): 35 | print('------ %s -------' % (kwargs,)) 36 | f = MultistageChannelFilter(**kwargs) 37 | 38 | size = 10000000 39 | 40 | top = gr.top_block() 41 | top.connect( 42 | blocks.vector_source_c([5] * size), 43 | f, 44 | blocks.null_sink(gr.sizeof_gr_complex)) 45 | 46 | print(f.explain()) 47 | 48 | t0 = time.clock() 49 | top.start() 50 | top.wait() 51 | top.stop() 52 | t1 = time.clock() 53 | 54 | print(size, 'samples processed in', t1 - t0, 'CPU-seconds') 55 | 56 | 57 | if __name__ == '__main__': 58 | # like SSB 59 | test_one_filter(input_rate=3200000, output_rate=8000, cutoff_freq=3000, transition_width=1200) 60 | 61 | # like WFM 62 | test_one_filter(input_rate=2400000, output_rate=240000, cutoff_freq=80000, transition_width=20000) 63 | 64 | # requires non-decimation resampling 65 | test_one_filter(input_rate=1000000, output_rate=48000, cutoff_freq=5000, transition_width=1000) 66 | -------------------------------------------------------------------------------- /shinysdr/test_signals.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Kevin Reid and the ShinySDR contributors 2 | # 3 | # This file is part of ShinySDR. 4 | # 5 | # ShinySDR is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ShinySDR is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with ShinySDR. If not, see . 17 | 18 | from __future__ import absolute_import, division, print_function, unicode_literals 19 | 20 | from twisted.trial import unittest 21 | 22 | from shinysdr.signals import no_signal, SignalType 23 | 24 | 25 | class TestSignalType(unittest.TestCase): 26 | def test_validation(self): 27 | self.assertRaises(TypeError, lambda: SignalType(kind='NONE', sample_rate=None)) 28 | self.assertRaises(ValueError, lambda: SignalType(kind='NONE', sample_rate=0.1)) 29 | self.assertRaises(ValueError, lambda: SignalType(kind='FOO', sample_rate=1.0)) 30 | self.assertRaises(ValueError, lambda: SignalType(kind='IQ', sample_rate=-1.0)) 31 | self.assertRaises(ValueError, lambda: SignalType(kind='IQ', sample_rate=0.0)) 32 | 33 | def test_constants(self): 34 | self.assertEqual( 35 | no_signal, 36 | SignalType(kind='NONE', sample_rate=0)) 37 | 38 | def test_eq(self): 39 | self.assertEqual( 40 | no_signal, 41 | no_signal) 42 | self.assertEqual( 43 | SignalType(kind='IQ', sample_rate=1), 44 | SignalType(kind='IQ', sample_rate=1)) 45 | self.assertNotEqual( 46 | no_signal, 47 | SignalType(kind='IQ', sample_rate=1)) 48 | self.assertNotEqual( 49 | SignalType(kind='IQ', sample_rate=1), 50 | SignalType(kind='IQ', sample_rate=2)) 51 | self.assertNotEqual( 52 | SignalType(kind='USB', sample_rate=1), 53 | SignalType(kind='LSB', sample_rate=1)) 54 | 55 | def test_sample_rate(self): 56 | self.assertIsInstance(SignalType(kind='IQ', sample_rate=1).get_sample_rate(), float) 57 | self.assertIsInstance(no_signal.get_sample_rate(), float) 58 | self.assertEqual(123, SignalType(kind='IQ', sample_rate=123).get_sample_rate()) 59 | self.assertEqual(0, no_signal.get_sample_rate()) 60 | 61 | def test_compatibility(self): 62 | def c(a, b): 63 | return a.compatible_items(b) 64 | 65 | self.assertTrue(c( 66 | SignalType(kind='IQ', sample_rate=1), 67 | SignalType(kind='IQ', sample_rate=2))) 68 | self.assertFalse(c( 69 | SignalType(kind='IQ', sample_rate=1), 70 | SignalType(kind='MONO', sample_rate=1))) 71 | -------------------------------------------------------------------------------- /shinysdr/test_twisted_ext.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2016, 2018 Kevin Reid and the ShinySDR contributors 3 | # 4 | # This file is part of ShinySDR. 5 | # 6 | # ShinySDR is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # ShinySDR is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with ShinySDR. If not, see . 18 | 19 | from __future__ import absolute_import, division, print_function, unicode_literals 20 | 21 | import textwrap 22 | 23 | from twisted.trial import unittest 24 | from twisted.internet import defer 25 | from twisted.internet.interfaces import IStreamClientEndpoint 26 | from twisted.internet import reactor as the_reactor 27 | 28 | from shinysdr.twisted_ext import SerialPortEndpoint, fork_deferred, test_subprocess 29 | 30 | 31 | class TestForkDeferred(unittest.TestCase): 32 | def test_success(self): 33 | outcomes = [] 34 | d = defer.Deferred() 35 | d2 = fork_deferred(d) 36 | d.addCallback(lambda x: outcomes.append('dc ' + x)) 37 | d2.addCallback(lambda x: outcomes.append('d2c ' + x)) 38 | self.assertEqual(outcomes, []) 39 | d.callback('value') 40 | self.assertEqual(outcomes, ['d2c value', 'dc value']) 41 | 42 | # TODO test of errback, stricter tests(?) 43 | 44 | 45 | class TestTestSubprocess(unittest.TestCase): 46 | def test_stdout_success(self): 47 | self.assertFalse(test_subprocess(['echo', 'x'], b'x', shell=False)) 48 | 49 | def test_stdout_failure(self): 50 | self.assertEqual(test_subprocess(['echo', 'y•'], b'x', shell=False), 51 | textwrap.dedent("""\ 52 | Expected `echo y•` to give output containing 'x', but the actual output was: 53 | y• 54 | """)) 55 | 56 | # TODO test command-not-found 57 | # TODO test stderr 58 | # TODO test shell mode 59 | 60 | 61 | class TestSerialPortEndpoint(unittest.TestCase): 62 | # We cannot rely on the existence of any serial ports or it being OK to try to open them, so this is just a smoke test: does creating the endpoint succeed? 63 | 64 | def test_smoke(self): 65 | endpoint = SerialPortEndpoint('NOTAREALSERIALPORT', the_reactor, baudrate=115200) 66 | IStreamClientEndpoint(endpoint) 67 | 68 | # TODO consider making an attempt to open that is known to fail 69 | -------------------------------------------------------------------------------- /shinysdr/units.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2016, 2017 Kevin Reid and the ShinySDR contributors 3 | # 4 | # This file is part of ShinySDR. 5 | # 6 | # ShinySDR is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # ShinySDR is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with ShinySDR. If not, see . 18 | 19 | """ 20 | Minimal units library. 21 | 22 | Used only for expressing units for display. Does not provide calculation or dimensions. 23 | """ 24 | 25 | from __future__ import absolute_import, division, print_function, unicode_literals 26 | 27 | from collections import namedtuple as _namedtuple 28 | 29 | from zope.interface import implementer as _implements 30 | 31 | from shinysdr.i.json import IJsonSerializable as _IJsonSerializable 32 | 33 | 34 | __all__ = [] # appended later 35 | 36 | 37 | class Unit(_namedtuple('Unit', [ 38 | 'symbol', 39 | 'si_prefix_ok'])): # TODO allow requesting binary prefixes? 40 | _implements(_IJsonSerializable) 41 | 42 | def to_json(self): 43 | return { 44 | 'type': 'Unit', 45 | 'symbol': self.symbol, 46 | 'si_prefix_ok': self.si_prefix_ok 47 | } 48 | 49 | def __str__(self): 50 | return self.symbol 51 | 52 | 53 | __all__.append('Unit') 54 | 55 | 56 | # TODO: reflectively put units into __all__ 57 | 58 | none = Unit('', True) 59 | s = Unit('s', True) 60 | degree = Unit('°', False) # degree of angle 61 | degC = Unit('°C', False) 62 | degF = Unit('°F', False) 63 | dB = Unit('dB', False) 64 | dBm = Unit('dBm', False) 65 | dBFS = Unit('dBFS', False) 66 | Hz = Unit('Hz', True) 67 | MHz = Unit('MHz', False) # TODO: Remove or refine this when si_prefix_ok is actually used 68 | ppm = Unit('ppm', False) 69 | --------------------------------------------------------------------------------