<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<meta name="generator" content="AsciiDoc 8.6.9" />
<title>UPABX: a UPnP ABX testing tool</title>
<style type="text/css">
/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
/* Default font. */
body {
font-family: Georgia,serif;
}
/* Title font. */
h1, h2, h3, h4, h5, h6,
div.title, caption.title,
thead, p.table.header,
#toctitle,
#author, #revnumber, #revdate, #revremark,
#footer {
font-family: Arial,Helvetica,sans-serif;
}
body {
margin: 1em 5% 1em 5%;
}
a {
color: blue;
text-decoration: underline;
}
a:visited {
color: fuchsia;
}
em {
font-style: italic;
color: navy;
}
strong {
font-weight: bold;
color: #083194;
}
h1, h2, h3, h4, h5, h6 {
color: #527bbd;
margin-top: 1.2em;
margin-bottom: 0.5em;
line-height: 1.3;
}
h1, h2, h3 {
border-bottom: 2px solid silver;
}
h2 {
padding-top: 0.5em;
}
h3 {
float: left;
}
h3 + * {
clear: left;
}
h5 {
font-size: 1.0em;
}
div.sectionbody {
margin-left: 0;
}
hr {
border: 1px solid silver;
}
p {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
ul, ol, li > p {
margin-top: 0;
}
ul > li { color: #aaa; }
ul > li > * { color: black; }
.monospaced, code, pre {
font-family: "Courier New", Courier, monospace;
font-size: inherit;
color: navy;
padding: 0;
margin: 0;
}
pre {
white-space: pre-wrap;
}
#author {
color: #527bbd;
font-weight: bold;
font-size: 1.1em;
}
#email {
}
#revnumber, #revdate, #revremark {
}
#footer {
font-size: small;
border-top: 2px solid silver;
padding-top: 0.5em;
margin-top: 4.0em;
}
#footer-text {
float: left;
padding-bottom: 0.5em;
}
#footer-badges {
float: right;
padding-bottom: 0.5em;
}
#preamble {
margin-top: 1.5em;
margin-bottom: 1.5em;
}
div.imageblock, div.exampleblock, div.verseblock,
div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
div.admonitionblock {
margin-top: 1.0em;
margin-bottom: 1.5em;
}
div.admonitionblock {
margin-top: 2.0em;
margin-bottom: 2.0em;
margin-right: 10%;
color: #606060;
}
div.content { /* Block element content. */
padding: 0;
}
/* Block element titles. */
div.title, caption.title {
color: #527bbd;
font-weight: bold;
text-align: left;
margin-top: 1.0em;
margin-bottom: 0.5em;
}
div.title + * {
margin-top: 0;
}
td div.title:first-child {
margin-top: 0.0em;
}
div.content div.title:first-child {
margin-top: 0.0em;
}
div.content + div.title {
margin-top: 0.0em;
}
div.sidebarblock > div.content {
background: #ffffee;
border: 1px solid #dddddd;
border-left: 4px solid #f0f0f0;
padding: 0.5em;
}
div.listingblock > div.content {
border: 1px solid #dddddd;
border-left: 5px solid #f0f0f0;
background: #f8f8f8;
padding: 0.5em;
}
div.quoteblock, div.verseblock {
padding-left: 1.0em;
margin-left: 1.0em;
margin-right: 10%;
border-left: 5px solid #f0f0f0;
color: #888;
}
div.quoteblock > div.attribution {
padding-top: 0.5em;
text-align: right;
}
div.verseblock > pre.content {
font-family: inherit;
font-size: inherit;
}
div.verseblock > div.attribution {
padding-top: 0.75em;
text-align: left;
}
/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
div.verseblock + div.attribution {
text-align: left;
}
div.admonitionblock .icon {
vertical-align: top;
font-size: 1.1em;
font-weight: bold;
text-decoration: underline;
color: #527bbd;
padding-right: 0.5em;
}
div.admonitionblock td.content {
padding-left: 0.5em;
border-left: 3px solid #dddddd;
}
div.exampleblock > div.content {
border-left: 3px solid #dddddd;
padding-left: 0.5em;
}
div.imageblock div.content { padding-left: 0; }
span.image img { border-style: none; vertical-align: text-bottom; }
a.image:visited { color: white; }
dl {
margin-top: 0.8em;
margin-bottom: 0.8em;
}
dt {
margin-top: 0.5em;
margin-bottom: 0;
font-style: normal;
color: navy;
}
dd > *:first-child {
margin-top: 0.1em;
}
ul, ol {
list-style-position: outside;
}
ol.arabic {
list-style-type: decimal;
}
ol.loweralpha {
list-style-type: lower-alpha;
}
ol.upperalpha {
list-style-type: upper-alpha;
}
ol.lowerroman {
list-style-type: lower-roman;
}
ol.upperroman {
list-style-type: upper-roman;
}
div.compact ul, div.compact ol,
div.compact p, div.compact p,
div.compact div, div.compact div {
margin-top: 0.1em;
margin-bottom: 0.1em;
}
tfoot {
font-weight: bold;
}
td > div.verse {
white-space: pre;
}
div.hdlist {
margin-top: 0.8em;
margin-bottom: 0.8em;
}
div.hdlist tr {
padding-bottom: 15px;
}
dt.hdlist1.strong, td.hdlist1.strong {
font-weight: bold;
}
td.hdlist1 {
vertical-align: top;
font-style: normal;
padding-right: 0.8em;
color: navy;
}
td.hdlist2 {
vertical-align: top;
}
div.hdlist.compact tr {
margin: 0;
padding-bottom: 0;
}
.comment {
background: yellow;
}
.footnote, .footnoteref {
font-size: 0.8em;
}
span.footnote, span.footnoteref {
vertical-align: super;
}
#footnotes {
margin: 20px 0 20px 0;
padding: 7px 0 0 0;
}
#footnotes div.footnote {
margin: 0 0 5px 0;
}
#footnotes hr {
border: none;
border-top: 1px solid silver;
height: 1px;
text-align: left;
margin-left: 0;
width: 20%;
min-width: 100px;
}
div.colist td {
padding-right: 0.5em;
padding-bottom: 0.3em;
vertical-align: top;
}
div.colist td img {
margin-top: 0.3em;
}
@media print {
#footer-badges { display: none; }
}
#toc {
margin-bottom: 2.5em;
}
#toctitle {
color: #527bbd;
font-size: 1.1em;
font-weight: bold;
margin-top: 1.0em;
margin-bottom: 0.1em;
}
div.toclevel0, div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
margin-top: 0;
margin-bottom: 0;
}
div.toclevel2 {
margin-left: 2em;
font-size: 0.9em;
}
div.toclevel3 {
margin-left: 4em;
font-size: 0.9em;
}
div.toclevel4 {
margin-left: 6em;
font-size: 0.9em;
}
span.aqua { color: aqua; }
span.black { color: black; }
span.blue { color: blue; }
span.fuchsia { color: fuchsia; }
span.gray { color: gray; }
span.green { color: green; }
span.lime { color: lime; }
span.maroon { color: maroon; }
span.navy { color: navy; }
span.olive { color: olive; }
span.purple { color: purple; }
span.red { color: red; }
span.silver { color: silver; }
span.teal { color: teal; }
span.white { color: white; }
span.yellow { color: yellow; }
span.aqua-background { background: aqua; }
span.black-background { background: black; }
span.blue-background { background: blue; }
span.fuchsia-background { background: fuchsia; }
span.gray-background { background: gray; }
span.green-background { background: green; }
span.lime-background { background: lime; }
span.maroon-background { background: maroon; }
span.navy-background { background: navy; }
span.olive-background { background: olive; }
span.purple-background { background: purple; }
span.red-background { background: red; }
span.silver-background { background: silver; }
span.teal-background { background: teal; }
span.white-background { background: white; }
span.yellow-background { background: yellow; }
span.big { font-size: 2em; }
span.small { font-size: 0.6em; }
span.underline { text-decoration: underline; }
span.overline { text-decoration: overline; }
span.line-through { text-decoration: line-through; }
div.unbreakable { page-break-inside: avoid; }
/*
* xhtml11 specific
*
* */
div.tableblock {
margin-top: 1.0em;
margin-bottom: 1.5em;
}
div.tableblock > table {
border: 3px solid #527bbd;
}
thead, p.table.header {
font-weight: bold;
color: #527bbd;
}
p.table {
margin-top: 0;
}
/* Because the table frame attribute is overriden by CSS in most browsers. */
div.tableblock > table[frame="void"] {
border-style: none;
}
div.tableblock > table[frame="hsides"] {
border-left-style: none;
border-right-style: none;
}
div.tableblock > table[frame="vsides"] {
border-top-style: none;
border-bottom-style: none;
}
/*
* html5 specific
*
* */
table.tableblock {
margin-top: 1.0em;
margin-bottom: 1.5em;
}
thead, p.tableblock.header {
font-weight: bold;
color: #527bbd;
}
p.tableblock {
margin-top: 0;
}
table.tableblock {
border-width: 3px;
border-spacing: 0px;
border-style: solid;
border-color: #527bbd;
border-collapse: collapse;
}
th.tableblock, td.tableblock {
border-width: 1px;
padding: 4px;
border-style: solid;
border-color: #527bbd;
}
table.tableblock.frame-topbot {
border-left-style: hidden;
border-right-style: hidden;
}
table.tableblock.frame-sides {
border-top-style: hidden;
border-bottom-style: hidden;
}
table.tableblock.frame-none {
border-style: hidden;
}
th.tableblock.halign-left, td.tableblock.halign-left {
text-align: left;
}
th.tableblock.halign-center, td.tableblock.halign-center {
text-align: center;
}
th.tableblock.halign-right, td.tableblock.halign-right {
text-align: right;
}
th.tableblock.valign-top, td.tableblock.valign-top {
vertical-align: top;
}
th.tableblock.valign-middle, td.tableblock.valign-middle {
vertical-align: middle;
}
th.tableblock.valign-bottom, td.tableblock.valign-bottom {
vertical-align: bottom;
}
/*
* manpage specific
*
* */
body.manpage h1 {
padding-top: 0.5em;
padding-bottom: 0.5em;
border-top: 2px solid silver;
border-bottom: 2px solid silver;
}
body.manpage h2 {
border-style: none;
}
body.manpage div.sectionbody {
margin-left: 3em;
}
@media print {
body.manpage div#toc { display: none; }
}
</style>
<script type="text/javascript">
/*<![CDATA[*/
var asciidoc = { // Namespace.
/////////////////////////////////////////////////////////////////////
// Table Of Contents generator
/////////////////////////////////////////////////////////////////////
/* Author: Mihai Bazon, September 2002
* http://students.infoiasi.ro/~mishoo
*
* Table Of Content generator
* Version: 0.4
*
* Feel free to use this script under the terms of the GNU General Public
* License, as long as you do not remove or alter this notice.
*/
/* modified by Troy D. Hanson, September 2006. License: GPL */
/* modified by Stuart Rackham, 2006, 2009. License: GPL */
// toclevels = 1..4.
toc: function (toclevels) {
function getText(el) {
var text = "";
for (var i = el.firstChild; i != null; i = i.nextSibling) {
if (i.nodeType == 3 /* Node.TEXT_NODE */) // IE doesn't speak constants.
text += i.data;
else if (i.firstChild != null)
text += getText(i);
}
return text;
}
function TocEntry(el, text, toclevel) {
this.element = el;
this.text = text;
this.toclevel = toclevel;
}
function tocEntries(el, toclevels) {
var result = new Array;
var re = new RegExp('[hH]([1-'+(toclevels+1)+'])');
// Function that scans the DOM tree for header elements (the DOM2
// nodeIterator API would be a better technique but not supported by all
// browsers).
var iterate = function (el) {
for (var i = el.firstChild; i != null; i = i.nextSibling) {
if (i.nodeType == 1 /* Node.ELEMENT_NODE */) {
var mo = re.exec(i.tagName);
if (mo && (i.getAttribute("class") || i.getAttribute("className")) != "float") {
result[result.length] = new TocEntry(i, getText(i), mo[1]-1);
}
iterate(i);
}
}
}
iterate(el);
return result;
}
var toc = document.getElementById("toc");
if (!toc) {
return;
}
// Delete existing TOC entries in case we're reloading the TOC.
var tocEntriesToRemove = [];
var i;
for (i = 0; i < toc.childNodes.length; i++) {
var entry = toc.childNodes[i];
if (entry.nodeName.toLowerCase() == 'div'
&& entry.getAttribute("class")
&& entry.getAttribute("class").match(/^toclevel/))
tocEntriesToRemove.push(entry);
}
for (i = 0; i < tocEntriesToRemove.length; i++) {
toc.removeChild(tocEntriesToRemove[i]);
}
// Rebuild TOC entries.
var entries = tocEntries(document.getElementById("content"), toclevels);
for (var i = 0; i < entries.length; ++i) {
var entry = entries[i];
if (entry.element.id == "")
entry.element.id = "_toc_" + i;
var a = document.createElement("a");
a.href = "#" + entry.element.id;
a.appendChild(document.createTextNode(entry.text));
var div = document.createElement("div");
div.appendChild(a);
div.className = "toclevel" + entry.toclevel;
toc.appendChild(div);
}
if (entries.length == 0)
toc.parentNode.removeChild(toc);
},
/////////////////////////////////////////////////////////////////////
// Footnotes generator
/////////////////////////////////////////////////////////////////////
/* Based on footnote generation code from:
* http://www.brandspankingnew.net/archive/2005/07/format_footnote.html
*/
footnotes: function () {
// Delete existing footnote entries in case we're reloading the footnodes.
var i;
var noteholder = document.getElementById("footnotes");
if (!noteholder) {
return;
}
var entriesToRemove = [];
for (i = 0; i < noteholder.childNodes.length; i++) {
var entry = noteholder.childNodes[i];
if (entry.nodeName.toLowerCase() == 'div' && entry.getAttribute("class") == "footnote")
entriesToRemove.push(entry);
}
for (i = 0; i < entriesToRemove.length; i++) {
noteholder.removeChild(entriesToRemove[i]);
}
// Rebuild footnote entries.
var cont = document.getElementById("content");
var spans = cont.getElementsByTagName("span");
var refs = {};
var n = 0;
for (i=0; i<spans.length; i++) {
if (spans[i].className == "footnote") {
n++;
var note = spans[i].getAttribute("data-note");
if (!note) {
// Use [\s\S] in place of . so multi-line matches work.
// Because JavaScript has no s (dotall) regex flag.
note = spans[i].innerHTML.match(/\s*\[([\s\S]*)]\s*/)[1];
spans[i].innerHTML =
"[<a id='_footnoteref_" + n + "' href='#_footnote_" + n +
"' title='View footnote' class='footnote'>" + n + "</a>]";
spans[i].setAttribute("data-note", note);
}
noteholder.innerHTML +=
"<div class='footnote' id='_footnote_" + n + "'>" +
"<a href='#_footnoteref_" + n + "' title='Return to text'>" +
n + "</a>. " + note + "</div>";
var id =spans[i].getAttribute("id");
if (id != null) refs["#"+id] = n;
}
}
if (n == 0)
noteholder.parentNode.removeChild(noteholder);
else {
// Process footnoterefs.
for (i=0; i<spans.length; i++) {
if (spans[i].className == "footnoteref") {
var href = spans[i].getElementsByTagName("a")[0].getAttribute("href");
href = href.match(/#.*/)[0]; // Because IE return full URL.
n = refs[href];
spans[i].innerHTML =
"[<a href='#_footnote_" + n +
"' title='View footnote' class='footnote'>" + n + "</a>]";
}
}
}
},
install: function(toclevels) {
var timerId;
function reinstall() {
asciidoc.footnotes();
if (toclevels) {
asciidoc.toc(toclevels);
}
}
function reinstallAndRemoveTimer() {
clearInterval(timerId);
reinstall();
}
timerId = setInterval(reinstall, 500);
if (document.addEventListener)
document.addEventListener("DOMContentLoaded", reinstallAndRemoveTimer, false);
else
window.onload = reinstallAndRemoveTimer;
}
}
asciidoc.install();
/*]]>*/
</script>
</head>
<body class="article">
<div id="header">
<h1>UPABX: a UPnP ABX testing tool</h1>
<span id="author">Jean-Francois Dockes</span><br />
<span id="email"><code><<a href="mailto:jf@dockes.org">jf@dockes.org</a>></code></span><br />
<span id="revdate">2017-11-16</span>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="paragraph"><p><strong>upabx</strong> is a tool for performing
<a href="https://en.wikipedia.org/wiki/ABX_test">ABX listening tests</a> using one
or several UPnP renderers, and, possibly, a hardware switchbox.</p></div>
<div class="paragraph"><p>The program controls:</p></div>
<div class="ulist"><ul>
<li>
<p>
What is played (maybe different for A/B if comparing e.g. compression
methods). This is set as UPnP URL, offset, duration.
</p>
</li>
<li>
<p>
The device it is played on (for comparing streamers): UPnP Renderer name
or uuid.
</p>
</li>
<li>
<p>
The line-level switch setting (for comparing preamps or sources, etc.).
</p>
</li>
<li>
<p>
The speaker-level switch setting. Speaker refers to the power level, we
normally use it to switch amps actually (still, could switch speakers,
too).
</p>
</li>
</ul></div>
<div class="paragraph"><p>These are defined in two sets in parameters, which define the test. The
definition file also supports text describing what is connected to what and
what it is we try to test.</p></div>
<div class="paragraph"><p>A <a href="#:ABXMKTEST">companion program</a> provides an easy GUI for creating test
definition files, including retrieving the URL from any renderer on which
it is playing or paused.</p></div>
</div>
</div>
<div class="sect1">
<h2 id="_the_abx_test_graphical_user_interface">The ABX test Graphical User Interface</h2>
<div class="sectionbody">
<div class="imageblock">
<div class="content">
<img src="upabxgui-1.png" alt="upabxgui-1.png" />
</div>
</div>
<div class="paragraph"><p>This should be largely self-explanatory. The program should be given a test
definition file as parameter on the command line:</p></div>
<div class="literalblock">
<div class="content">
<pre><code>/path/to/upabx/bin/upabx /path/to/test_definition_file</code></pre>
</div></div>
<div class="paragraph"><p>You can use the <em>Player</em> section to play samples <strong>A</strong>, <strong>B</strong> and <strong>X</strong> as you like.</p></div>
<div class="paragraph"><p>The <em>Range</em> section allows you to restrict the part which is played (start
and end). Just click the buttons while one of the samples is playing.</p></div>
<div class="paragraph"><p>Once you’re tired of comparing, use the <em>Choice</em> section to tell the
application if you think that the <strong>X</strong> sample is the same as <strong>A</strong> or
<strong>B</strong>. Then click <em>Confirm</em>, which will store your choice, and increment the
test number.</p></div>
<div class="paragraph"><p>Repeat until the end of the test sequence.</p></div>
<div class="paragraph"><p>The A and B samples can be the same URL playing on different renderers
(needs switch or mixer), or different URLs (for comparing encodings or
performances), or the same renderer and URL if you are using a hardware
switch to compare preamplifiers or amplifiers. More on the sample
parameters <a href="#testparams">further down</a>.</p></div>
</div>
</div>
<div class="sect1">
<h2 id="ABXMKTEST">The upabxmktest test creation utility</h2>
<div class="sectionbody">
<div class="imageblock">
<div class="content">
<img src="upabxmktest-1.png" alt="upabxmktest-1.png" />
</div>
</div>
<div class="paragraph"><p>The utility makes it easy to create test files. Especially, it will find
out the list of renderers present on the network, and it will retrieve an
URL and the associated metadata from a Renderer, so you can set the track
to play for each of the A/B sections just by setting it to play (or pause)
somewhere, and fetching it from the GUI.</p></div>
<div class="paragraph"><p>Set the parameters to use in the <em>A</em> section, then set the changed parameters
in the <em>B</em> section.</p></div>
<div class="paragraph"><p>The URL <em>Choose</em> buttons will open a popup, where you will be able to
select a renderer from a list. The utility will then fetch the URL and
metadata for the track currently playing or paused on the Renderer.</p></div>
<div class="paragraph"><p>When you are done, use the <em>File</em> menu to save the test definition. The
file format details are described in an <a href="#testconfig">annex</a>.</p></div>
</div>
</div>
<div class="sect1">
<h2 id="TESTRESULTS">Test results</h2>
<div class="sectionbody">
<div class="paragraph"><p>The test results are stored in <em>~/.local/share/upabx</em> and are named like
<em>date</em>-<em>time</em>-<code>result</code>. They look like the following:</p></div>
<div class="listingblock">
<div class="content">
<pre><code>[ Contents of the test parameters file included here]
Mon Nov 13 17:45:37 2017 Choice: B : True
Mon Nov 13 17:45:59 2017 Choice: A : True
Mon Nov 13 17:46:16 2017 Choice: A : True
Mon Nov 13 17:46:36 2017 Choice: B : True
Mon Nov 13 17:46:55 2017 Choice: A : True
Mon Nov 13 17:47:11 2017 Choice: B : True
Mon Nov 13 17:47:26 2017 Choice: A : True
Mon Nov 13 17:47:42 2017 Choice: A : True
Mon Nov 13 17:47:52 2017 Choice: A : True
Mon Nov 13 17:48:00 2017 Choice: B : True
Mon Nov 13 17:48:00 2017 Test completed: 10/10</code></pre>
</div></div>
</div>
</div>
<div class="sect1">
<h2 id="globalconf">The global configuration</h2>
<div class="sectionbody">
<div class="paragraph"><p>Most of the program parameters are found in the test parameter files (see
next). However, some values are defined in a global configuration file
(<em>~/.config/upabx/upabx.conf</em>). The file contains <em>name = value</em> lines.</p></div>
<div class="dlist"><dl>
<dt class="hdlist1">
switchhost
</dt>
<dd>
<p>
Host name or IP address for the switch interface.
</p>
</dd>
<dt class="hdlist1">
seqlen
</dt>
<dd>
<p>
Default test sequence length (which itself has a default:
10). This can be redefined in the test configuration for individual tests.
</p>
</dd>
<dt class="hdlist1">
switchport
</dt>
<dd>
<p>
TCP port for the switch interface (default: 8080).
</p>
</dd>
</dl></div>
<div class="paragraph"><p>Only <code>switchhost</code> needs to be defined and only if you are actually using a
hardware switch.</p></div>
</div>
</div>
<div class="sect1">
<h2 id="_building_operating_environment">Building, operating environment</h2>
<div class="sectionbody">
<div class="paragraph"><p>The program only runs on Linux/X11 at the moment. It should not be hard to
port it to Windows (or Mac OS X). As far as I can see it should mostly be a
matter of adjusting the configuration file locations. <strong>libupnpp</strong> is already
ported to both systems, and its Python bindings should not be a major
issue. Please contact me if there is an interest: <a href="mailto:jf@dockes.org">jf@dockes.org</a></p></div>
<div class="paragraph"><p>All the code lives
<a href="https://opensourceprojects.eu/p/upabx/code/">here</a>. The GUI code is in
the <em>abx</em> directory, the test creation code is in <em>abxmktest</em>.</p></div>
<div class="paragraph"><p>The Makefile will bundle the Python code into two self-contained Python
zipped scripts inside the <em>bin</em> directory: <em>bin/upabx</em> and
<em>bin/upabxmktest</em>. Just run make:</p></div>
<div class="literalblock">
<div class="content">
<pre><code>cd /path/to/upabx
make</code></pre>
</div></div>
<div class="paragraph"><p>You need to have <strong>libupnpp</strong> and <strong>libupnpp-bindings</strong> installed, and you can
then start <em>upabx</em> or <em>upabxmktest</em> like any Linux command: copy them
somewhere in your PATH, or specify their absolute path, e.g.:</p></div>
<div class="literalblock">
<div class="content">
<pre><code>/path/to/upabx/bin/upabxmktest
/path/to/upabx/bin/upabx /path/to/test_definition_file</code></pre>
</div></div>
<div class="paragraph"><p>You will need a <a href="#switchhard">hardware switch</a> (or at least a mixer) if
you want to compare electronics (from streamers/DACs to power
amps). <strong>upabx</strong> interfaces with a small <a href="#switchapp">local web
application</a>, typically running on a Raspberry Pi, for controlling the
switch (the GUI does not need to run on the same host as the switch
controller). Any kind of relay which can interface more or less directly
with Raspberry Pi GPIO pins should be workable.</p></div>
</div>
</div>
<div class="sect1">
<h2 id="switchapp">The hardware switch interface application</h2>
<div class="sectionbody">
<div class="paragraph"><p>The switch interface application is a trivial WEB interface based on the
<a href="http://bottlepy.org/docs/dev/">Python Bottle framework</a>, and on the
<a href="https://pypi.python.org/pypi/RPi.GPIO">Raspberry PI GPIO Python
interface</a> (I only tried it on a Raspberry PI, I guess that it should be
easy to port to another GPIO-equipped computer if an appropriate Python
module is available).</p></div>
<div class="paragraph"><p>The application should be able to control any relay which can interface
with the Pi On/Off GPIO lines (details on my crappy hardware implementation
follow further down).</p></div>
<div class="paragraph"><p>I was too lazy to package the code, which does not really need an
installation (can run from anywhere). It is found under the <code>hwctl</code>
directory in the <strong>upabx</strong> source tree.</p></div>
<div class="sect2">
<h3 id="_the_switch_application_configuration">The switch application configuration</h3>
<div class="paragraph"><p>The configuration file is expected to be found in
<em>/etc/audioswitch.conf</em>. It defines the GPIO pin numbers used to control
the line and speaker relays. The contents are as follows:</p></div>
<div class="listingblock">
<div class="content">
<pre><code># GPIO pin numbers for the speaker relay and line input relay
speaker_channel = 4
line_channel = 17</code></pre>
</div></div>
<div class="paragraph"><p>To be accessible from the network, the app should be started as:</p></div>
<div class="literalblock">
<div class="content">
<pre><code>switcherapp-run.py -a 0.0.0.0</code></pre>
</div></div>
<div class="paragraph"><p>The default port is <em>8080</em>. You can change it by passing <em>-p portnum</em> on the
command line.</p></div>
<div class="paragraph"><p>Example <strong>systemd</strong> service file (change <em>mylogin</em> to the real user name):</p></div>
<div class="listingblock">
<div class="content">
<pre><code>[Unit]
Description=HW Switch manager HTTP server
After=network.target
[Service]
User=mylogin
ExecStart=/usr/bin/python2 /home/mylogin/hwctl/switcherapp-run.py -a 0.0.0.0
Restart=always
[Install]
WantedBy=multi-user.target</code></pre>
</div></div>
</div>
<div class="sect2">
<h3 id="_the_switch_application_interface">The switch application interface</h3>
<div class="paragraph"><p>The server presents four URLs on the network: <em>/spka</em>, <em>/spkb</em>, <em>/linea</em>,
<em>/lineb</em>. I’d wager that you can guess what they do. Example command line
usage from another machine (<strong>upabx</strong> uses Python code to do the same):</p></div>
<div class="literalblock">
<div class="content">
<pre><code>wget -o /dev/null http://swhost:swport/spkb</code></pre>
</div></div>
<div class="paragraph"><p>When using the application from <strong>upabx</strong>, the host and port are set in the
<strong>upabx</strong> <a href="#globalconf">global configuration file</a>.</p></div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="testconfig">Annex: test definition file details</h2>
<div class="sectionbody">
<div class="paragraph"><p>Tests are defined in simple configuration files. The configuration file
name for a test is passed to the GUI on the command line.</p></div>
<div class="paragraph"><p>This section describes the file contents in detail, but you will usually
create the tests with the <a href="#ABXMKTEST">upabxmktest</a> program, so you
should not need to care about the details.</p></div>
<div class="paragraph"><p>The parameter values are set as:</p></div>
<div class="literalblock">
<div class="content">
<pre><code>paramname = some value</code></pre>
</div></div>
<div class="paragraph"><p>Anything which does not look like this will be a comment.</p></div>
<div class="paragraph"><p>There are two sections in the file: the first one defines the base
parameters (<strong>A</strong>), the second one (<em>[test]</em>) defines only the parameters
which change for <strong>B</strong>.</p></div>
<div class="listingblock">
<div class="content">
<pre><code># A parameters
param = somevalue
otherparam = othervalue
[test]
# B parameters. Only otherparam changes
otherparam = yetanothervalue-for-the-B-sample</code></pre>
</div></div>
<div class="sect2">
<h3 id="testparams">Test parameters</h3>
<div class="paragraph"><p>The following values can be set. Except if mentionned otherwise, they all
can be changed in the <em>[test]</em> section.</p></div>
<div class="dlist"><dl>
<dt class="hdlist1">
renderer
</dt>
<dd>
<p>
renderer name
The UPnP <em>friendly name</em> or UUID for the Media Renderer device to use.
</p>
</dd>
<dt class="hdlist1">
url
</dt>
<dd>
<p>
The URL used by the Media Server for the track to play. <em>upabxmktest</em>
will find this for you. Else, this is not always obvious to
determine. <strong>upplay</strong> has a handy <em>Copy URL</em> entry in the directory listing
popup menu. Also, <strong>upnp-inspector</strong>.
</p>
</dd>
<dt class="hdlist1">
meta
</dt>
<dd>
<p>
The metadata for the track to play. This will be set by
<em>upabxmktest</em>, but not all renderers require it, so you may not have to
bother about it if you create a test by hand. Some renderers don’t like
URLs though. For example, <strong>upmpdcli</strong> needs <code>checkcontentformat = 0</code> if
there is no MIME format in the metadata.
</p>
</dd>
<dt class="hdlist1">
seek
</dt>
<dd>
<p>
start of play position in seconds.
The player will seek to this position before starting play. This can differ
in the second section, which can be handy if you are comparing
interpretations and not audio quality.
</p>
</dd>
<dt class="hdlist1">
dursecs
</dt>
<dd>
<p>
sample duration in seconds.
Set to 0 to let the complete track play. Can’t change for the <em>B</em> section
(what for ?).
</p>
</dd>
<dt class="hdlist1">
seqlen
</dt>
<dd>
<p>
number of tests to perform.
You won’t be able to save a result before the sequence is complete. The
default is defined in the global configuration.
</p>
</dd>
<dt class="hdlist1">
line
</dt>
<dd>
<p>
line-level switch position.
Set to <em>A</em> or <em>B</em>. The value is not related to the A/B choices, <em>A</em> and <em>B</em>
are the values used by the hardware switch control application for turning
the relay on or off.
</p>
</dd>
<dt class="hdlist1">
spk
</dt>
<dd>
<p>
speaker-level switch position.
Set to <em>A</em> or <em>B</em>. Ditto no relationship to the A/B sections.
</p>
</dd>
</dl></div>
<div class="paragraph"><p>If neither <em>line</em> nor <em>spk</em> are set, <strong>upabx</strong> will not try to access the
hardware switch interface.</p></div>
</div>
</div>
</div>
</div>
<div id="footnotes"><hr /></div>
<div id="footer">
<div id="footer-text">
Last updated
2018-02-03 16:42:14 CET
</div>
</div>
</body>
</html>