Skip to content

Instantly share code, notes, and snippets.

@dklann
Created January 19, 2021 18:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dklann/506ee2a805c2c97de8ccde2bf4739b8d to your computer and use it in GitHub Desktop.
Save dklann/506ee2a805c2c97de8ccde2bf4739b8d to your computer and use it in GitHub Desktop.
<?php
/*
* Nautel RDS Text injector.
*
* This script accepts text from several sources:
*
* - an external entity that knows to set "artist" and "title" (and
* possibly other parameters) in the GET request (e.g., Spinitron
* Metadata Push) (non-interactive mode).
* Example Spinitron configuration:
* http://my-rds-proxy-hostname.example.com:9000/?title=%sn%&artist=%an%&release=%dn%&playlist=%wn%spinnote=%se%
*
* - this script (via a web browser), by setting 'our_text' in the GET
* request (interactive mode).
*
* - this script (via the command line), by setting 'artist' and 'title'
* OR 'our_text' as command line arguments (non-interactive mode).
*
* Used interactively (via a web browser) this script uses the Bonsai
* CSS styling library. This is the thing that gives the web page such
* an awesome look!
*
* See also these links for more details:
*
* - https://spinitron.com/about/help/metadata-push.html
* - https://gist.github.com/spinitron/1950987cf8deb1013a350a698268e6e0
* - socat(1) for testing and simulating an RDS encoder
* - https://github.com/bonsaicss/bonsai.css
*/
/*
* RDS network settings.
*
* Set $rds_address to the IP address of the Nautel transmitter. Per
* Nautel docs, the port is hard-coded (and not configurable) to 7005.
*
* Set $rds_address to localhost and some high port for testing.
*
* For testing, also run something like this command:
*
* socat 'TCP4-LISTEN:localhost:9000,reuseaddr,fork' -
*
* in a terminal window to see what this script will send to the RDS
* encoder.
*
*/
//$rds_address = '192.168.0.1';
$rds_port = 7005;
$rds_address = '127.0.0.1';
/*
* Pre-programmed "quickie" button values.
* Add more items here to display more buttons on the page.
* Remember to keep the character count to 64 or fewer.
*/
$buttons = array(
"CALL SIGN Morning Program",
"CALL SIGN: Generic Tag",
"CALL SIGN: Other Generic Tag",
);
/*
* HTML details.
*
* The header contains the site boilerplate and page style details.
*/
$header =<<<EndOfHeader
<!DOCTYPE HTML>
<head lang='en'>
<meta http-equiv=EXPIRES content=0>
<meta http-equiv=CONTENT-TYPE content="text/html; charset=UTF-8">
<meta charset='utf-8'>
<title>RDS Text Injector</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<meta name='apple-mobile-web-app-title' content='CALL SIGN RDS Text Injector'>
<meta name='application-name' content='CALL SIGN RDS Text Injector'>
<meta name='msapplication-config' content='icons/browserconfig.xml'>
<meta name='theme-color' content='#ffffff'>
<meta name="description" content="Simple web form used to accept text and send it to the Nautel transmitter RDS encoder.">
<link id='stylesheet' rel='stylesheet' href='dist/bonsai.min.css'>
<link rel='manifest' href='icons/manifest.json'>
<link rel='shortcut icon' href='icons/favicon.ico'>
<style>
section {
margin: 2em 0 4em;
}
.grid-demo {
grid-auto-rows: minmax(50px, auto);
}
.grid-demo > * {
background: var(--primary);
min-height: 3rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -1px rgba(0, 0, 0, .06);
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
color: white;
text-align: center;
}
.grid-demo > *:nth-child(even) {
background: #3f5aa4;
}
</style>
</head>
<body style="--mv:20px; --p:0; --bg:#f5f5f5; --c:#202020">
<div style="--wrapper:1000px; --gutter:3vw;">
<img src="logo.png" alt="CALL SIGN Logo" style="--m:40px auto 0; --d:block">
<h1 class="primary" style="--ta:center">RDS Text Injector</h1>
<p style="--ta:center; --maxw:450px; --m:0 auto 3rem">
Fill in the blank or touch a pre-programmed button.
</p>
EndOfHeader;
/*
* This section contains the HTML input form that accepts user-entered
* text.
*/
$input_form =<<<EndOfInputForm
<section>
<blockquote>
<mark>
This updates the live radio data broadcast system ('RDBS', or simply 'RDS')
text as soon as you press the button or press &lt;Enter&gt;.
</mark>
</blockquote>
</section>
<form name="rdsdata_user" action="index.php" method="GET">
<div class="grid" style="--col: 1">
<label>RDS Text
<input type="text" name="our_text" maxlength="64" size="64" placeholder="Your meaningful text here (up tp 64 characters)..." onfocus="this.value=''" />
</label>
</div>
<br />
<input type="submit" value="Tell the world..." />
</form>
EndOfInputForm;
/*
* The header for the "button form" that contains pre-selected text
* buttons for quick RDS updates.
*/
$button_form_start =<<<EndOfButtonFormStart
<form name="rdsdata_generic" action="index.php" method="GET">
<div class="grid" style="--col: 2">
EndOfButtonFormStart;
$button_form_end =<<<EndOfButtonFormEnd
</div>
</form>
EndOfButtonFormEnd;
/*
* The page footer that wraps everything up.
*/
$footer =<<<EndOfFooter
</div> <!-- style="wrapper:1000px..." -->
</body>
EndOfFooter;
// Make it work on the command line too.
if ($argc > 1)
parse_str(implode('&', array_slice($argv, 1)), $_GET);
// Send the first part of the web page if called interactively
// ('artist' is unset).
if (!array_key_exists('artist', $_GET)) { echo $header; }
// The guts of this script: figure out how we were called, and send
// the message text to the RDS encoder (aka Nautel transmitter).
if (array_key_exists('artist', $_GET) ||
array_key_exists('our_text', $_GET)) {
// Prolly being called from the Spinitron Metadata Push.
if (array_key_exists('artist', $_GET) &&
$_GET['artist'] != '') {
// The bare minumum message text.
$message_text = '"' . $_GET['title'] . '" by ' . $_GET['artist'];
// DISABLED THIS DUE TO LACK OF SPACE IN RDS (limit is 64 characters)
// if (array_key_exists('playlist', $_GET) &&
// $_GET['playlist'] != '' ) {
// // Add the show name (aka playlist name) if it is
// // available.
// // $message_text = 'Now playing on ' . $_GET['playlist'] . ': ' . $message_text;
// $message_text = $_GET['playlist'] . ': ' . $message_text;
// }
// if (array_key_exists('spinnote', $_GET) &&
// $_GET['spinnote'] != '') {
// // Add the spin note if it is available.
// $message_text = $message_text . ' (' . $_GET['spinnote'] . ')';
// }
} elseif (array_key_exists('our_text', $_GET) &&
$_GET['our_text'] != '') {
// Prolly being called from above (this script).
$message_text = $_GET['our_text'];
} else {
// Uh. Who knows how we got here...
$message_text = '';
}
/*
* Convert the message text to plain ASCII and send it to the
* transmitter.
*/
if ($message_text) {
// Convert text encoding if needed. Spinitron sends UTF-8. Idk if the RDS encoder expects a
// specific encoding or if it is transparent, in which case what car radios expect.
$display_text = iconv('UTF-8', 'ASCII//TRANSLIT', $message_text);
// Generate the "commands" to send to the RDS encoder. We could also truncate over-long messages
// but I think they do no harm. How to terminate the command? CR or LF, or both, what order, how many?
$message = "TEXT=$display_text\r\n";
// Make sure strlen() returns the byte length.
ini_set('mbstring.func_overload', 0);
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (socket_connect($socket, $rds_address, $rds_port)) {
$sent_bytes = socket_send($socket, $message, strlen($message), MSG_EOF);
}
}
// Show what we sent if this is running on the local network(s).
if (($_SERVER['REMOTE_ADDR'] == '127.0.0.1') ||
($_SERVER['REMOTE_ADDR'] == 'localhost') ||
(preg_match('/192\.168\..*/', $_SERVER['REMOTE_ADDR']))) {
if ($message_text) {
echo '<p>Last message sent: "' . $message_text . '"</p>';
}
}
}
/*
* Likewise with the main body of the page with the forms, only send
* it if interactive ('artist' is unset).
*/
if (!array_key_exists('artist', $_GET)) {
echo $button_form_start;
foreach ($buttons as $button) {
$element = '<button name="our_text" type="submit" value="' . $button . '">' . $button . '</button>';
echo $element;
}
echo $button_form_end;
echo '<hr />';
echo $input_form;
echo $footer;
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment