diff --git a/Dynamic_RDS.php b/Dynamic_RDS.php
index 2b4ee62..0a351b9 100644
--- a/Dynamic_RDS.php
+++ b/Dynamic_RDS.php
@@ -491,6 +491,18 @@ function DynRDSFastUpdate() {
$.get('api/plugin/Dynamic_RDS/FastUpdate');
}
+// The RDS Settings group shares a single callback, but only the Style Text and
+// Update Rate fields are re-read by the Engine on UPDATE. Everything else in the
+// group is consumed when the transmitter is set up, so it still needs an FPP restart.
+var DynRDSLiveRDSSettings = ['DynRDSPSStyle', 'DynRDSRTStyle',
+ 'DynRDSPSUpdateRate', 'DynRDSRTUpdateRate'];
+
+function DynRDSRDSSettingsUpdate(key) {
+ if (DynRDSLiveRDSSettings.indexOf(key) !== -1) {
+ DynRDSFastUpdate();
+ }
+}
+
function DynRDSPiBootUpdate(key) {
$.post('api/plugin/Dynamic_RDS/PiBootChange/' + encodeURIComponent(key),
JSON.stringify(Object.assign({}, pluginSettings)));
@@ -519,15 +531,13 @@ function ScriptStreamProgressDialogDone() {
* Display all settings groups
*/
function displaySettingsGroups(array $settings): void {
- PrintSettingGroup("DynRDSRDSSettings", getRDSStyleGuideHTML(), "", 1, "Dynamic_RDS", "UpdateDynRDSTransmitterChildren");
+ PrintSettingGroup("DynRDSRDSSettings", getRDSStyleGuideHTML(), "", 1, "Dynamic_RDS", "DynRDSRDSSettingsUpdate");
PrintSettingGroup("DynRDSTransmitterSettings", "", "", 1, "Dynamic_RDS", "DynRDSTransmitterFrequencyUpdate");
// Wrap Audio in div id to make it easy to hide/show for Si4713
echo '
';
- PrintSettingGroup("DynRDSAudioSettings", "",
- "indicates a live change to transmitter, no FPP restart required",
- 1, "Dynamic_RDS", "DynRDSFastUpdate");
+ PrintSettingGroup("DynRDSAudioSettings", "", "", 1, "Dynamic_RDS", "DynRDSFastUpdate");
echo '
';
PrintSettingGroup("DynRDSPowerSettings", "", "", 1, "Dynamic_RDS", "DynRDSPiBootUpdate");
diff --git a/Dynamic_RDS_Engine.py b/Dynamic_RDS_Engine.py
index 4d84ee1..e327c3c 100755
--- a/Dynamic_RDS_Engine.py
+++ b/Dynamic_RDS_Engine.py
@@ -60,7 +60,7 @@ def read_config():
config['DynRDSQN8066BufferGain'] = totalGain % 18 // 3
if not (os.path.exists('/bin/mpc') or os.path.exists('/usr/bin/mpc')):
- config['DynRDSmpcEnable'] = 0
+ config['DynRDSmpcEnable'] = '0'
logging.getLogger().setLevel(config['DynRDSEngineLogLevel'])
logging.info('Config %s', config)
@@ -294,13 +294,24 @@ def excessive(msg, *args, **kwargs):
writeStatus()
elif line == 'UPDATE':
+ priorMPCEnable = config['DynRDSmpcEnable']
read_config()
mqtt.publish('config', json.dumps(config, indent=8))
- if (transmitter is not None and transmitter.active):
- for key in rdsValues:
- rdsValues[key] = ''
+ # MPC only ever sources {T}, so toggling it while idle would otherwise leave
+ # the last polled title stale. During a playlist {T} comes from FPP media
+ # events, so leave it alone.
+ if config['DynRDSmpcEnable'] != priorMPCEnable and not activePlaylist:
+ rdsValues['{T}'] = ''
+ nextMPCUpdate = datetime.now()
+ if transmitter is not None:
+ # Buffers cache their delay at construction, so re-sync on config changes
+ # or the status panel and the chip disagree on the update rate
+ if getattr(transmitter, 'PS', None) is not None:
+ transmitter.PS.delay = int(config['DynRDSPSUpdateRate'])
+ transmitter.RT.delay = int(config['DynRDSRTUpdateRate'])
updateRDSData()
- transmitter.update()
+ if transmitter.active:
+ transmitter.update()
writeStatus()
elif line == 'START':
@@ -376,9 +387,23 @@ def excessive(msg, *args, **kwargs):
if not activePlaylist and transmitter is not None and transmitter.active and config['DynRDSmpcEnable'] == "1" and datetime.now() > nextMPCUpdate:
logging.debug('Processing mpc')
nextMPCUpdate = datetime.now() + timedelta(seconds=12)
- # TODO: Error handling might be needed here if the mpc execution has an issue
+
# TODO: Future idea to handle multiple fields from mpc, but I've not seen them used yet. [{A}%artist%][{T}%title%][{N}%track%]
- mpcLatest = subprocess.run(['mpc', 'current', '-f', '%title%'], stdout=subprocess.PIPE, check=False).stdout.decode('utf-8').strip()
+ try:
+ mpcLatest = subprocess.run(['mpc', 'current', '-f', '%title%'],
+ stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
+ check=False, timeout=2,
+ encoding='utf-8', errors='replace').stdout.strip()
+ except subprocess.TimeoutExpired:
+ # mpc or MPD is wedged - back off so RDS group output isn't stalled every 12 seconds
+ logging.warning('mpc timed out - backing off for 60 seconds')
+ nextMPCUpdate = datetime.now() + timedelta(seconds=60)
+ mpcLatest = rdsValues['{T}']
+ except OSError as error:
+ logging.warning('mpc could not be run (%s) - backing off for 60 seconds', error)
+ nextMPCUpdate = datetime.now() + timedelta(seconds=60)
+ mpcLatest = rdsValues['{T}']
+
if rdsValues['{T}'] != mpcLatest:
rdsValues['{T}'] = mpcLatest
updateRDSData()
diff --git a/README.md b/README.md
index 2dd0f71..3113772 100644
--- a/README.md
+++ b/README.md
@@ -135,7 +135,7 @@ During the plugin install, an example script is copied to the FPP `media/scripts
All settings are on the plugin's config page, reachable from **Status/Control -> Dynamic RDS**. The page auto-detects your transmitter over I2C and hides the settings that don't apply to it.
> [!NOTE]
-> Settings marked with a lightning bolt icon take effect immediately on the transmitter — no FPP restart needed.
+> Settings marked with a lightning bolt icon take effect immediately — no FPP restart needed.
### RDS Settings
| Setting | Default | Notes |
diff --git a/settings.json b/settings.json
index 1feb232..00dbad1 100644
--- a/settings.json
+++ b/settings.json
@@ -174,7 +174,7 @@
"name": "DynRDSQN8066AmpPower",
"description": "Amp Power (0-100)",
"tip": "Adjust the power output for the amplifier after the transmitter chip. This is controlled by PWM output.",
- "suffix": " - Controlled by PWM",
+ "suffix": " - Controlled by PWM",
"restart": 0,
"reboot": 0,
"type": "number",
@@ -273,7 +273,7 @@
"max": 20,
"step": 1,
"default": 0,
- "suffix": ""
+ "suffix": ""
},
"DynRDSQN8066SoftClipping": {
"name": "DynRDSQN8066SoftClipping",
@@ -284,7 +284,7 @@
"checkedValue": "1",
"uncheckedValue": "0",
"default": 1,
- "suffix": ""
+ "suffix": ""
},
"DynRDSQN8066AGC": {
"name": "DynRDSQN8066AGC",
@@ -295,7 +295,7 @@
"checkedValue": "1",
"uncheckedValue": "0",
"default": 0,
- "suffix": ""
+ "suffix": ""
},
"DynRDSSi4713TestAudio": {
"name": "DynRDSSi4713TestAudio",
@@ -327,19 +327,20 @@
"name": "DynRDSPSStyle",
"description": "PS Style Text (8 chars per update)",
"tip": "Sent 8 characters at a time. Program Service is the most commonly displayed part of RDS.",
- "restart": 1,
+ "restart": 0,
"reboot": 0,
"type": "text",
"size": 32,
"maxlength": 64,
- "default": "{T}|{A}[|{P} of {C}]|Merry|Christ-| -mas!"
+ "default": "{T}|{A}[|{P} of {C}]|Merry|Christ-| -mas!",
+ "suffix": ""
},
"DynRDSPSUpdateRate": {
"name": "DynRDSPSUpdateRate",
"description": "PS Update Rate",
"tip": "Interval between updating the 8 characters being sent. It takes ~1 second to send the 8 characters and some radios only display the text after receiving the full group twice.",
- "suffix": "seconds",
- "restart": 1,
+ "suffix": "seconds ",
+ "restart": 0,
"reboot": 0,
"type": "number",
"min": 3,
@@ -351,19 +352,20 @@
"name": "DynRDSRTStyle",
"description": "RT Style Text",
"tip": "Recommended to send 32 characters at a time, but can send up to 64 characters. Radio Text is intended for longer message with a slower update rate.",
- "restart": 1,
+ "restart": 0,
"reboot": 0,
"type": "text",
"size": 64,
"maxlength": 256,
- "default": "{T}[ by {A}][|Track {P} of {C}] Merry Christmas!"
+ "default": "{T}[ by {A}][|Track {P} of {C}] Merry Christmas!",
+ "suffix": ""
},
"DynRDSRTUpdateRate": {
"name": "DynRDSRTUpdateRate",
"description": "RT Update Rate",
"tip": "Interval between updating the 32-64 characters being sent. It takes ~4 seconds to send the 64 characters and some radios only display the text after receiving the full group twice.",
- "suffix": "seconds",
- "restart": 1,
+ "suffix": "seconds ",
+ "restart": 0,
"reboot": 0,
"type": "number",
"min": 3,
@@ -476,7 +478,7 @@
"checkedValue": "1",
"uncheckedValue": "0",
"default": 0,
- "suffix": ""
+ "suffix": ""
},
"DynRDSmqttEnable": {
"name": "DynRDSmqttEnable",