ShinySDR runs as two independent processes. The server performs all DSP operations and communicates with the radio hardware. It is written in Python (2, not 3) and uses the frameworks GNU Radio for DSP and Twisted for networking. The client implements the user interface (including audio input/output); it lives entirely within a single web page and uses standard JavaScript, HTML, and web APIs. Communication between the client and the server is via HTTP and WebSocket.
The server code has three principal functions:
TODO more here
TODO client architecture, require.js, etc.
One of the design principles of the client is that reloading the page should lose no information. All state should be recorded on the server, in localStorage, or in the URL.
ShinySDR plugins are defined based on the Twisted plugin system. A plugin is a Python module or package which exports objects implementing one of the defined plugin interfaces such as:
shinysdr.interfaces.ModeDef
to add new modes/demodulators.
shinysdr.interfaces.ClientResourceDef
to add JS code or other web resources to be loaded by the client (ClientResourceDef
). This can be used to define new user interface elements; it should generally support other plugin elements (such as UI for a demodulator) rather than having effects on its own.
To be loaded, a plugin must be placed somewhere on your Python module search path (PYTHONPATH
) in the shinysdr.plugins
package. (In the future, there may be a method to add plugins from the configuration file.)
To add functionality, an instance of one of the plugin definitions must be declared at the top level of the module; for example, in basic_demod.py we can see code like
@implementer(IModulator) class AMModulator(gr.hier_block2, ExportedState): ... pluginDef_am = ModeDef(mode='AM', info=EnumRow(label='AM', sort_key=BASIC_MODE_SORT_PREFIX + 'AM'), demod_class=AMDemodulator, mod_class=AMModulator)
The important thing is that the module contains the ModeDef
; the variable name does not matter.
[TODO: formally document plugin interfaces; embed/hyperlink here]
No part of the server requires a “build” operation; this simplifies the development process. It contains no C or C++ code, avoiding the need for a build system aware of OS/distribution/compiler variations.
Avoid inheritance in favor of composition, particularly across module boundaries. (We're not doing so well at sticking to this consistently.)
TODO: Write more of this.
Automated testing and linting that should be done regularly:
Run automated tests of Python code by running trial shinysdr. The trial
utility is part of Twisted.
(If you are using a MacPorts-packaged version of Twisted, note that it will have a Python-version suffix such as trial-2.7
. I don't know whether any other package systems have a similar issue.)
Lint code by running ./lint.sh
Run automated tests of JavaScript code by visiting /test/
.
Manual testing that can be done (when likely relevant):
Visit the regular main page (and poke around to see if anything is obviously broken).
Create a receiver and select every available mode. (TODO: This ideally would be covered by the automated tests, but currently is not due to cleanup cases.)
Using an appropriate config file, verify that all Device
s you have hardware for work at least to the point of displaying a spectrum.
For the telemetry modes (APRS, Mode S), tune appropriately and receive at least one message. (TODO: Add sample messages to the SimulatedDevice
so real-world signals are unnecessary.)
Visit the tools to make sure they work.
Visit a block page (e.g. append /radio/monitor to the main page URL).
Run shinysdr --create filename and try using the resulting config file.
Every nontrivial source code file shall begin with copyright and license information as seen in existing files (with an appropriate statement of the copyright on that particular file).
lint.sh
runs a set of linting tools (JSHint, Pylint, and flake8) which should produce no new complaints.
Follow PEP 8 except as noted. Existing violations (most notably, camel-cased identifiers) are not to be imitated.
PEP 8 Exception: Do not break (wrap) lines except at non-arbitrary, semantically significant positions.
Good example (one line per method parameter, self
-that-is-always-around bundled with the name):
def __init__(self, name=None, rx_driver=nullExportedState, tx_driver=nullExportedState, vfo_cell=None, components={}):
Bad example (just packed into 79 characters):
def __init__(self, name=None, rx_driver=nullExportedState, tx_driver=nullExportedState, vfo_cell=None, components={}):
PEP 8 Exception: Blank lines shall match the indentation of their neighbors.
Every Python file (that is not an empty __init__.py
or otherwise special) shall declare:
from __future__ import absolute_import, division, print_function, unicode_literals
All object attributes should use the double underscore “private” prefix unless they are intended to be used otherwise.
Every module should define __all__
appropriately; listing all names it intends to be used by other code.
TODO