Scope of the Management Functions
-
Starting and closing the system.
-
Run-time configurations, called start-up profiles.
-
Distribution of the system across machine and OS boundaries.
Starting and Closing the System
Start the system (of your choice) by double clicking a desktop icon. Close the system by closing the UI. Sounds pretty like any application. Read on for the details.
A lot of effort has gone into this particular aspect. It is simple to run a single executable application on a single machine. When that application consists of many processes across one or more machine and one or more operating systems things become a lot more complex. When the number of processes started to increase and the possible permutation of what could be started where and what sequence they needed to be started in it was obvious that to make this a viable system to use, things had to be automated.
Although the startup can be as manual as required I have been striving to further automate the process and make it look and feel more like a single application to the user. Starting erlink-sr is now simply a matter of lauching a program, actually a batch or command file. An example file looks like this:
"C:\Program Files\erl5.5.1\bin\erl.exe" -sname switcher -setcookie sdrnet -pz D:\erlink\erlang\erlink-sw\ebin -pz D:\erlink\erlang\erlink-sm\ebin -pz D:\erlink\erlang\erlink-sync-node\ebin -pz D:\erlink\erlang\erlink-hw-node\ebin -config erlinksr-base -run erlinksr start
where ,
- sname - sets the erlang name of the central node (no reason to ever change this name)
- setcookie - sets a value that all communicating nodes must know., a simple security system (again no reason to change this)
- pz - sets the paths to the erlang directories, in a delivered system there would probably only be one path, this is dev so there are a few.
The next two are slightly more interesting.
- config - sets the file that will be read to determine how to start the system (see later)
- run - this will run the start function in the module erlinksr (should never need to be changed)
Configuration File
The configuration file read in the above startup looks like this:
[{erlinksr, [{profiles, {[{sdr1000Laptop, localhost, "D:\\erlink\\cpp\\erlink-vcpp\\release\\"}]}}]}].
It is in fact one erlang term. Ignore the multitude of brackets,
they are the result of the way the resource file is written (more on
that later). The important parts are
the first name which is the name of the application (erlinksr). What
follows is text that will be substituted in the resource file under the
profile section. Its this that actually drives what will be started.
The profile name, in this case 'sdr1000laptop' ensures all the right
stuff is loaded and the system configures itself correctly.
NOTE: there were sections in here about distributed operation across machines. I have removed these until I do the porting again as things will have changed. It also makes this easier to read as it concentrates on one thing.
Before the system can be started of course the resource file must be correct.
Configuring the Resource File
This is a very much cut down resource file as I have separated out the two simple cases of everything on the same machine which I am currently concentrating on from the six or so distributed configurations I had dreamed up. This is therefore somewhat more readable than the previous example I put on this page. The file itself is a human readable erlang data struture consisting of tuples and lists. However, it's simple to understand and most changes will be copy/paste operations and a few edits. The most likely issue with the file is missing or extra commas. Erlang is very particular about line termination characters so inspect the file and note where commas are needed and where there should be no terminator. The final terminator at the end of the file must be a full stop.
Selecting the Start-up Profile
Generally select this through the batch file as described above. The line in the configuration file will rewrite the entire 'profiles' section just leaving the actual profile to be started. However, you can do it manually by uncommenting the appropriate line and using the a startup file that does not have the last two switches and then at the erlang node type:
application:start(erlinksr).
| { application, erlinksr, [ {description, "ERLINK-SR"}, {vsn, "1.0"}, {mod, {erlinksr,[sysmon]}}, {env, [ %% %% The available profiles, where to launch them and the local path to bin. %% This file is for co-located running (hence localhost), the distributed scenarios are in %% a separate file to keep things from looking too cluttered. In a distributed scenario there %% are profile clusters, one for each machine. %% Only one of these should be active although if started from the automated batch file this %% section is overwritten with the appropriate profile. %% {profiles, { [ %% Profile (1) SDR1000 and DttSp profile {sdr1000DttSp, localhost, "D:\\erlink\\cpp\\erlink-vcpp\\release\\"} %% Profile (2) SDR1000 and jDSP profile %%{sdr1000JDsp, localhost, "D:\\erlink\\cpp\\erlink-vcpp\\release\\"} ] } }, %% %% The available profile implementations. This is the set of processes to start for the profile. %% The name must match one of the profile names in the profile section. There can be only one profile %% implementation with that name. In order to aid debugging these are multiple implementations that %% start everything or miss the UI, DSP or both. Each of these pprocesses can be started independently %% as required but for normal running always use the FULL implementation. %% %% Profile (1) implementation {sdr1000DttSp, {"sdr1000", [applicationA, dttspDsp, sdr1000Hw, streamIn, streamOut, ui]}}, %%{sdr1000DttSp, {"sdr1000", [applicationA, dttspDsp, sdr1000Hw, streamIn, streamOut]}}, %%{sdr1000DttSp, {"sdr1000", [applicationA, sdr1000Hw, streamIn, streamOut]}}, %% %% Profile (2) implementation {sdr1000JDsp, {"sdr1000_jdsp", [applicationB, jDsp, sdr1000Hw, streamIn, streamOut, ui]}}, %%{sdr1000JDsp, {"sdr1000_jdsp", [applicationB, sdr1000Hw, streamIn, streamOut, ui]}}, %%{sdr1000JDsp, {"sdr1000_jdsp", [applicationB, jDsp, sdr1000Hw, streamIn, streamOut]}}, %%{sdr1000JDsp, {"sdr1000_jdsp", [applicationB, sdr1000Hw, streamIn, streamOut]}}, %% %% The process instantiations called up by the profiles. %% These are the processes that get started, one per entry in the profile implementation. Many of these will be %% shared between profiles. %% %% Process command lines. Three types of process can be started: %% erlProc - a standard Erlang process that will be started at the main erlang node (i.e. no separate node is created) %% Note: this can also be a 'C' Port if the Erlang process loads a shared library (the HW node is like this. %% cNode - a pure C/C++ node using the Erlang C libraries. This is always a separate node. %% jNode - a pure Java node using the Erlang jInterface library. This is always a separate node. Note also that this %% supports any language that targets the JVM and integrates with Java. Jython currently front-ends all jNodes %% the application nodes (the only difference is the profile parameter passed in) {applicationA, {sm, erlProc, {app, start, [sm, sm, sdr1000, 'switcher@LT-BOBC-01', ui, stream, dttspDsp, sdr1000Hw, 'D:\\erlink\\state']}}}, {applicationB, {sm, erlProc, {app, start, [sm, sm, sdr1000_jdsp, 'switcher@LT-BOBC-01', ui, stream, jDsp, sdr1000Hw, 'D:\\erlink\\state']}}}, %% the DSP nodes, completely different implementations {dttspDsp, {dttspDsp, cNode, "erlink-dttsp -n dttspDsp -h LT-BOBC-01 -c dttspDsp -y null -b N -s switcher@LT-BOBC-01"}}, {jDsp, {jDsp, jNode, "jython D:\\erlink\\jython\\erlink_jdsp\\src\\DspMain.py -n jDsp -h LT-BOBC-01 -c jDsp -s switcher@LT-BOBC-01 -m sm"}}, %% the SDR1000 hardware controller {sdr1000Hw, {sdr1000Hw, erlProc, {hw_node, start, [sdr1000Hw, 'switcher@LT-BOBC-01', sdr1000]}}}, %% the streams and UI are common {streamIn, {stream, cNode, "erlink-instr -n streamIn -h LT-BOBC-01 -c stream -y null -b N -s switcher@LT-BOBC-01"}}, {streamOut, {stream, cNode, "erlink-outstr -n streamOut -h LT-BOBC-01 -c stream -s switcher@LT-BOBC-01"}}, {ui, {ui, jNode, "jython D:\\erlink\\jython\\erlink-console\\src\\UiMain.py -n ui -h LT-BOBC-01 -c ui -s switcher@LT-BOBC-01 -m sm -p D:\\erlink\\state"}} ] } ] }. |
The three sections of the file (profiles, profile implementation and process instantiation) are quite well commented so there is little point in repeating that in text. The only changes to this file for a particular insallation is to globally change the computer name (LT-BOBC-01 to your computer name).
Closing the System
Just close the UI. However, this is hiding the true nature of the system. The UI is not a central piece, just a node. What happens is that on system startup the main process erlinksr enters a message loop waiting for a shutdown message. When the UI is closed it sends this shutdown message and an orderly shutdown then takes place including the UI. You could also shutdown the system by typing:application:stop(erlinksr).
into the command window that is running the system.
The Capability System
A profile drives the actual starting of the system, what stream managers start, what DSP, whether a harware controller is required and on what machine these start. However, a profile is not enough to drive exactly what features a system provides to a user, which should of course match what functions are available in the started system. This is where capabilities come in. The profile will drive what application starts, or more precisely what application behaviour is started. That behaviour which is implemented as a state machine provides a capability set that is sent to any node requiring it. Currently the capability set is simple but there is a lot of scope here for it to drive the precise presentation and other aspects of the system. A capability at the moment looks like this (held of course in erlang which runs the application):
s_radio_capability() ->
[
%%key type
{profile, { sdr1000}},
%%key
tx,
vfo, metering,
displays,
{general,
{ false, true,
true, true}},
%%key
multiwatch,
variable-if, lots more...
{dsp,
{ true,
true}}
].
It's just a dictionary which is divided into related sections. It also carries the profile name it is associated with. This simple structure at present will drive the UI. Take two very different applications, the full SDR1000 support with DttSp, supporting displays, metering, multi-watch etc and the embroyonic jDSP.
The jDSP has a capability (and a much simpler application behaviour) that looks like this:
s_radio_jdsp_capability() ->
[
%%key type
{profile, { sdr1000_jdsp}},
%%key
TX,
vfo, metering,
displays,
{general,
{ false, true,
false, false}},
%%key
multiwatch,
variable-if, lots more...
{dsp,
{ false,
false}}
]..
The presentation of each is shown below:


Now , when the profile says there is no display or metering things look a bit different. The only panel available is JDspRadio which is a much cut down radio panel.
Behind the Scenes
When the UI is starting it is sent the capability map before it even starts to build the UI. The map then drives how the UI is built in its outer containers, display or not etc. When it's time to load the panels into the tree view, each panel is queried to see if it is compatible with the capabilities. If it is it is added, if not the user won't see it. Some panels will in the future cope with different capability sets and adjust their presentation accordingly.
The system transcript for the two panels shows the different processes started for the profile.
For the full system:

and for the jDSP system:

Actually, apart from a different DSP they start the same processes. The name of the command file can be seen in the window title.
Synchronisation in a Distributed System
NOTE: left in for information only. It is likely to change once I get back to looking at these scenarios.
The final part of this is how communications are managed in a distributed system. Firstly, nodes that communicate via the switcher do so using the normal mechanisms available in erlang, The whole thing is location transparent and no special measures are taken by the application. For managing the sampled data however shared memory is utilised and this does require special handling when the shared memory is synchronised between system. This is necessary in scenarios 1-3 above and is why only these scenarios actually start synchronisation nodes..
The synchronisation process is slightly different from other processes in the system. It is the only process that sends messages that do not go through the switcher. This is for reasons of efficiency as these messages carry significant binary data which is a set of samples and has to be delivered in the shortest possible time. Even so compression will be required where it is possibly to apply compression. This implementation is relatively simple at present and caters only for the synchronisation of raw and processed data and does not use compression. It will need to be extended to manage at least display data and also to provide the compression/decompression within the synchronization stream.
There are three scenarios explicitly catered for - more flexibility may be required!
-
Server has the hardware and runs the DSP. Client runs the GUI and outputs the audio stream locally. This is probably the most common remote operating scenario. Technically the server does Stream In and DSP and the Client does Stream Out. The location of the other nodes is not relevant.
-
Server does DSP only and the Client does everything else. This is more likely an experimental setup where the DSP needs to be separated but the hardware is on another machine. Developing/testing DSP on a different OS or embedded system is an obvious use for this.
-
Server has the hardware and therefore does the Stream In but all else is on the Client. This is probably the quickest way to have one machine with multiple hardware or develop on a machine not having hardware, say a general purpose laptop but have an easy way to hook up to multiple hardware setups.
-
There is a forth scenario for testing which will copy the raw data to the processed data without doing DSP. This is for testing the sync processes and the streaming on one machine.
There are other distributed scenarios that don't need synchronisation such as just running GUI on the client to split the processing when machines are in the same room.
It turns out that in the first three scenarios a single role for each participant can be defined. It is expected that there will be different profiles for each scenario and that scenarios are not run concurrently in one application.
There are three participants.
-
The Stream In or the DSP node which always starts a synchronisation process. To this end the parameters for Stream In from the profile need to identify (in addition to the normal parameters):
A boolean to say if synchronization is required in this profile. If not nothing will be done regardless of any synchronization processes spawned.
The class of the local synchronisation node. If (1) is true then whenever raw data is put into shared memory a SYNC message will be sent to the class in (2). Note the SYNC message IS sent via the switcher as this is the control channel, separate from the data channel.
-
The local synchronisation process. Note, local can be either the client or server side. It is just the side where synchronisation starts.
-
The remote synchronisation process.
Parameters for a synchronisation process are (in addition to the normal parameters):
-
The address of the node to send data or replies to. Note this is not the class as messages go direct.
The role for the node as defined below.
A synchronisation node has a single role which is part of the profile and passed on startup. The following roles are defined.
-
SYNC_READ_RAW - reads raw data from SHM and dispatches to remote node (only responds to a Sync message).
-
SYNC_WRITE_RAW - receive raw data on the data channel and writes to the SHM raw area (only responds to data message).
-
SYNC_READ_PROC - reads processed data from SHM and dispatches to remote node (only responds to a Sync message).
-
SYNC_WRITE_PROC - receive processed data on the data channel and writes to the SHM processed area (only responds to data message).
-
SYNC_READ_EXCHANGE - reads raw data from SHM and dispatches, waits for processed data reply and writes to SHM(only responds to a Sync message).
-
SYNC_WRITE_EXCHANGE - receives raw data on the data channel and writes to SHM, reads and replies with processed data (only responds to data message).
The following are legal combinations which could be validated by the application startup.
Scenario 1 - Client synchronisation process: SYNC_WRITE_PROC, Server: SYNC_READ_PROC
Scenario 2 - Client synchronisation process: SYNC_READ_EXCHANGE, Server: SYNC_WRITE_EXCHANGE
Scenario 3 - Client synchronisation process: SYNC_WRITE_RAW, Server: SYNC_READ_RAW
Scenario 4 - Client synchronisation process: SYNC_READ_RAW, Server: SYNC_WRITE_PROC