node-red-contrib-musiccast
Version:
A Node-RED collection for monitoring and controlling a Yamaha Musiccast network.
937 lines (798 loc) • 40.7 kB
HTML
<!--
Apache-2.0
Copyright (c) 2023 Vahdettin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
-->
<script src="resources/node-red-contrib-musiccast/musiccast-ui.js"></script>
<script type="text/javascript">
RED.nodes.registerType("musiccast-config", {
category: "config",
defaults: {
name: {value: "", required: true},
device_list: {
value: {
"418729e3-5f04-4786-9913-b270a9d3779e": {
"name": "",
"address": "",
"model": "0000",
"input_list": "",
"zone_list": "",
"serial": "xxxxxxxxxxx",
"udn": "xxxxxxxxxxxxx",
"uuid": "418729e3-5f04-4786-9913-b270a9d3779e",
"status": "pending",
"mod_date": "2022-08-06T01:58:23.930Z"
}
},
validate: function (value) {
let dupCheck = {};
if (Object.keys(value).length === 0) {
return false;
}
for (const device in value) {
if (value.hasOwnProperty(device)) {
if (!value[device].address || !value[device].name) {
return false;
}
if (value[device].zone === 'unspecified') {
return false;
}
if (value[device].model === '00000') {
return false;
}
if (dupCheck[value[device].serial]) {
return false;
}
if (dupCheck[value[device].address]) {
return false;
}
if (dupCheck[value[device].name]) {
return false;
}
dupCheck[value[device].serial] = true;
dupCheck[value[device].address] = true;
dupCheck[value[device].name] = true;
}
}
return true;
}, required: true
},
f_use_debug: {value: false, required: true},
f_blank_stale_status: {value: false, required: true},
list_language: {value: "en", required: true,},
command_timeout: {
value: "2000",
validate: function (value) {
if (parseInt(value) < 1 || parseInt(value) > 10000) {
return false;
}
return true;
}, required: true
},
zone_list: {
value: {
main: {
id: "main",
alt_id: "Main_Zone",
label: "Main"
},
zone2: {
id: "zone2",
alt_id: "Zone_2",
label: "Zone 2"
},
zone3: {
id: "zone3",
alt_id: "Zone_3",
label: "Zone 3"
},
zone4: {
id: "zone4",
alt_id: "Zone_4",
label: "Zone 4"
}
}, required: true
},
main: {value: "Main", required: true},
zone_2: {value: "Zone 2", required: true},
zone_3: {value: "Zone 3", required: true},
zone_4: {value: "Zone 4", required: true},
services_timeout: {
value: "5000",
validate: function (value) {
if (parseInt(value) < 1 || parseInt(value) > 10000) {
return false;
}
return true;
}, required: true
}
},
label: function () {
return this.name ? this.name : 'My Musiccast';
},
color: "#9c86bc",
oneditprepare: function () {
let ui = new MCUI("config", this, RED);
let mc_data = new MCData();
const Fields = ui.cFields;
let mc = this;
let dupCheck = {};
const statii = {
'pending': {
class: 'fa fa-question-circle'
},
'searching': {
class: 'fa fa-hourglass'
},
'seen': {
class: 'fa fa-check-square'
},
'error': {
class: 'fa fa-exclamation-triangle'
}
};
/*
Refresh capabilities for all devices
*/
let reDiscover = function () {
ui.d("reDiscover");
const elDeviceEditor = $("#node-input-devices-container");
let devices = elDeviceEditor.editableList('items');
devices.each(function (i) {
let device = $(this);
device.find(Fields.address).prop("disabled", true);
device.find(Fields.name).prop("disabled", true);
device.find(Fields.model).prop("disabled", true);
try {
ui.getData('discover', {
address: device.find(Fields.address).val()
}, function (err, response) {
console.log(response)
if (response && JSON.parse(response)) {
try {
let data = JSON.parse(response);
let device_description = data.device_description || {};
if (device_description.modelName) {
device.find(Fields.model).val(device_description.modelName);
}
let feature_data = data.feature_data || {};
if (feature_data.input_list) {
device.find(Fields.input_list).html(JSON.stringify(feature_data.input_list || {}));
}
if (feature_data.zone_list) {
device.find(Fields.zone_list).html(JSON.stringify(feature_data.zone_list || {}));
}
device.find(Fields.address).prop("disabled", false);
device.find(Fields.name).prop("disabled", false);
device.find(Fields.model).prop("disabled", false);
RED.notify("Information was updated for " + device.find(Fields.name).val() + ".", 'info');
} catch (err) {
RED.notify("Information could not be updated for " + device.find(Fields.name).val() + ".", 'warning');
}
} else {
device.find(Fields.address).prop("disabled", false);
device.find(Fields.name).prop("disabled", false);
device.find(Fields.model).prop("disabled", false);
RED.notify("Invalid response from " + device.find(Fields.name).val() + ".", 'warning');
}
});
} catch (err) {
RED.notify("Error while updating " + device.find(Fields.name).val() + ".", 'warning');
device.find(Fields.address).prop("disabled", false);
device.find(Fields.name).prop("disabled", false);
device.find(Fields.model).prop("disabled", false);
}
});
}
/*
Device editor
*/
const elDeviceEditor = $("#node-input-devices-container");
elDeviceEditor.css('min-height', '250px').css('min-width', '200px').editableList({
header: $("<div>").append($.parseHTML("<div style='width:calc(8% - 10px); margin-left: 5px; display: inline-grid'>Status</div><div style='width:calc(32% - 25px); margin-left: 5px; text-align: left; display: inline-grid'>Address</div><div style='width:calc(32% - 16px); margin-left: 5px; text-align: left; display: inline-grid'>Name</div><div style='width:calc(32% - 16px); margin-left: 5px; text-align: left; display: inline-grid'>Model</div>")),
removeItem: function (data) {
if (data.device && data.device.address) {
if (dupCheck[data.device.address]) dupCheck[data.device.address] = false;
}
if (data.device && data.device.serial) {
if (dupCheck[data.device.serial]) dupCheck[data.device.serial] = false;
}
if (data.device && data.device.name) {
if (dupCheck[data.device.name]) dupCheck[data.device.name] = false;
}
},
addItem: function (container, i, opt) {
container.css({
overflow: 'hidden',
whiteSpace: 'nowrap'
});
let device = opt.device || {
name: "",
address: "",
model: "0000",
status: 'pending',
uuid: ui.uuidv4(),
serial: ui.uuidv4(),
updated: new Date().toISOString()
};
//backwards compat for devices that were around before
if (!device.uuid) {
device.uuid = ui.uuidv4();
}
if (!device.updated) {
device.updated = new Date().toISOString()
}
//default for devices that were around before
if (!device.status) {
device.status = 'seen';
}
let row = $('<div/>').appendTo(container);
dupCheck[device.name] = true;
dupCheck[device.address] = true;
dupCheck[device.serial] = true;
let statusIClass = statii[device.status].class;
let statusIField = $('<i/>', {
class: statusIClass,
style: "margin-left:5px; width:calc(8% - 10px);"
}).appendTo(row);
let addressClass = "musiccast-config-input-address node-input-device-address-value";
let addressField = $('<input/>', {
class: addressClass,
type: "text",
style: "margin-left:5px; width:calc(32% - 25px);",
placeholder: '192.168.x.x',
title: "address",
value: device.address
}).appendTo(row);
let nameClass = "musiccast-config-input node-input-device-name-value";
let nameField = $('<input/>', {
class: nameClass,
type: "text",
style: "margin-left:5px; width:calc(32% - 16px);",
placeholder: 'Living room',
title: "name",
value: device.name
}).appendTo(row);
let modelField = $('<select/>', {
class: "musiccast-config-input node-input-device-model-value",
style: "margin-left:5px; width:calc(32% - 16px);",
}).appendTo(row);
let serialClass = "musiccast-config-input node-input-device-serial-value";
let serialField = $('<input/>', {
class: serialClass,
type: "text",
style: "visibility: hidden; display: inline-block;width: 1px;",
value: device.serial
}).appendTo(row);
let udnClass = " musiccast-config-input node-input-device-udn-value";
let udnField = $('<input/>', {
class: udnClass,
type: "text",
style: "visibility: hidden; display: inline-block;width: 1px;",
value: device.udn
}).appendTo(row);
let statusClass = "musiccast-config-input node-input-device-status-value";
let statusField = $('<input/>', {
class: statusClass,
type: "text",
style: "visibility: hidden; display: inline-block;width: 1px;",
value: device.status
}).appendTo(row);
let inputListClass = "node-input-device-input_list-value";
let inputListField = $('<span/>', {
class: inputListClass,
html: device.input_list,
style: "visibility: hidden; display: inline-block;width: 1px;",
}).appendTo(row);
let zoneListClass = "node-input-device-zone_list-value";
let zoneListField = $('<span/>', {
class: zoneListClass,
html: device.zone_list,
style: "visibility: hidden; display: inline-block;width: 1px;",
}).appendTo(row);
let uuidClass = "node-input-device-uuid-value";
let uuidField = $('<span/>', {
class: uuidClass,
html: device.uuid,
style: "visibility: hidden; display: inline-block;width: 1px;",
}).appendTo(row);
let updatedClass = "node-input-device-updated-value";
let updatedField = $('<span/>', {
class: updatedClass,
html: device.updated,
style: "visibility: hidden; display: inline-block;width: 1px;",
}).appendTo(row);
ui.popModels(modelField, function (err, result) {
if (result && result === true) {
modelField.val(device.model);
}
});
nameField.change(function () {
let name = nameField.val();
if (dupCheck[name] || name === null) {
nameField.addClass("input-error");
} else {
nameField.removeClass("input-error");
}
});
addressField.change(function () {
let addressVal = addressField.val();
let re = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
if (
dupCheck[addressVal] ||
!addressVal.match(re)
) {
addressField.addClass("input-error");
return;
}
addressField.removeClass("input-error");
nameField.val('');
modelField.val('0000');
statusField.val('searching');
addressField.prop("disabled", true);
nameField.prop("disabled", true);
modelField.prop("disabled", true);
statusIField.attr('class', statii[statusField.val()].class);
/*
Device discovery
*/
ui.getData('discovery', {
address: addressVal,
mc: mc.id
}, function (err, response) {
let modelName = "0000";
if (response || err) {
addressField.prop("disabled", false);
nameField.prop("disabled", false);
modelField.prop("disabled", false);
}
if (response && JSON.parse(response)) {
let data = JSON.parse(response);
if (data && data.error) {
statusField.val('error');
if (data.error.message === 'timeout') {
RED.notify("No device found at the provided address. Please enter the rest of the information manually.", 'warning');
} else {
RED.notify("No Musiccast device found at the provided address. Please confirm the address is correct or enter the rest of the information manually and hope for the best.", 'warning');
}
} else if (data) {
let device_description = data.device_description || {};
let feature_data = data.feature_data || {};
if (feature_data.input_list) {
inputListField.html(JSON.stringify(feature_data.input_list || {}));
}
if (feature_data.zone_list) {
zoneListField.html(JSON.stringify(feature_data.zone_list || {}));
}
if (device_description.modelName) {
statusField.val('seen');
if (ui.models[device_description.modelName]) {
modelName = device_description.modelName
}
modelField.val(modelName);
nameField.val(device_description.friendlyName || device_description.modelName);
udnField.val(device_description.udn || '');
uuidField.val(device_description.udn || '');
serialField.val(device_description.serialNumber || '');
} else {
RED.notify("A non-Musiccast device was found at the provided address. Please confirm the address is correct or enter the rest of the information manually and hope for the best.", 'warning');
statusField.val('pending');
}
}
} else {
RED.notify("No device found at the provided address. Please enter the rest of the information manually.", 'warning');
statusField.val('pending');
}
statusIField.attr('class', statii[statusField.val()].class);
});
});
},
sortable: true,
removable: true,
addButton: "add device",
buttons: [{
label: "refresh all",
icon: "fa fa-refresh",
title: "refresh all",
click: function (evt) {
reDiscover();
}
}]
});
/*
Start
*/
for (const d in mc.device_list) {
if (mc.device_list.hasOwnProperty(d)) {
elDeviceEditor.editableList('addItem', {
device: mc.device_list[d],
hash: d
});
}
}
//backwards compatibility
if (!mc.main) mc.main = mc_data.zones.main;
if (!mc.zone_2) mc.zone_2 = mc_data.zones.zone_2;
if (!mc.zone_3) mc.zone_3 = mc_data.zones.zone_3;
if (!mc.zone_4) mc.zone_4 = mc_data.zones.zone_4;
ui.popLangs();
let coll = document.getElementsByClassName("musiccast-form-section-collapsible");
let i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function () {
this.classList.toggle("active");
let content = this.nextElementSibling;
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
},
oneditsave: function () {
const ui = new MCUI("config", this, RED);
const Fields = ui.cFields;
const elDeviceEditor = $("#node-input-devices-container");
let devices = elDeviceEditor.editableList('items');
let mc = this;
mc.zone_member_list = [];
//backwards compatibility
mc.main = Fields.main.val()
mc.zone_2 = Fields.zone_2.val()
mc.zone_3 = Fields.zone_3.val()
mc.zone_4 = Fields.zone_4.val()
try {
mc.zone_list = {
main: {
id: "main",
alt_id: "Main_Zone",
label: Fields.main.val()
},
zone2: {
id: "zone2",
alt_id: "Zone_2",
label: Fields.zone_2.val(),
},
zone3: {
id: "zone3",
alt_id: "Zone_3",
label: Fields.zone_3.val(),
},
zone4: {
id: "zone4",
alt_id: "Zone_4",
label: Fields.zone_4.val()
}
}
} catch (err) {
console.warn(err)
}
devices.each(function (i) {
let device = $(this);
//force an ok status assuming the user knows it was offline or otherwise valid
mc.device_list[device.find(ui.Fields.uuid).html()] =
{
name: device.find(Fields.name).val(),
address: device.find(Fields.address).val(),
model: device.find(Fields.model).val(),
input_list: device.find(Fields.input_list).html(),
zone_list: device.find(Fields.zone_list).html(),
serial: device.find(Fields.serial).val(),
udn: device.find(Fields.udn).val(),
uuid: device.find(Fields.uuid).html(),
status: 'seen',
updated: device.find(Fields.updated).val(),
};
});
}
});
</script>
<script type="text/html" data-template-name="musiccast-config">
<link rel="stylesheet" href="resources/node-red-contrib-musiccast/style.css"/>
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-config-input-name" placeholder="Home"/>
</div>
<div class="form-row node-input-devices-container-row musiccast-form-row">
<label for="node-input-devices-container-div" style="vertical-align:top"><i class="fa fa-crosshairs"></i> Device
editor</label>
<div class="musiccast-form-section-description"><p>Drag devices into the order you want them to appear throughout Node-RED.</p></div>
<ol id="node-input-devices-container"></ol>
</div>
<div class="form-row musiccast-form-row">
<label for="node-input-section-zones-title"><i class="fa fa-map-marker"></i> Zone editor</label>
<span id="node-input-section-zones-title"></span>
</div>
<div class="musiccast-form-section-description"><p>Zones can be configured with labels to override their defaults
which can be then specified in variables and messages. Note that the system name can always be used even when
the labels are customized.</p>
<div class="musiccast-form-section-content">
<div class="form-row musiccast-form-row ">
<label for="node-config-input-main"> Main (main)</label>
<input type="text" id="node-config-input-main" style="width:200px" placeholder="Main">
</div>
<div class="form-row musiccast-form-row ">
<label for="node-config-input-zone_2"> Zone 2 (zone2)</label>
<input type="text" id="node-config-input-zone_2" style="width:200px" placeholder="Zone 2">
</div>
<div class="form-row musiccast-form-row ">
<label for="node-config-input-zone_3"> Zone 3 (zone3)</label>
<input type="text" id="node-config-input-zone_3" style="width:200px" placeholder="Zone 3">
</div>
<div class="form-row musiccast-form-row ">
<label for="node-config-input-zone_4"> Zone 4 (zone4)</label>
<input type="text" id="node-config-input-zone_4" style="width:200px" placeholder="Zone 4">
</div>
</div>
</div>
<div class="form-row musiccast-form-row">
<label for="node-input-section-zones-title"><i class="fa fa-gear"></i> General</label>
<span id="node-input-section-general_settings-title"></span>
</div>
<div class="musiccast-form-section-description"><p>The node status displays on the palette can be cleared automatically after 30 seconds.</p>
<div class="musiccast-form-section-content">
<div class="form-row musiccast-form-row ">
<label for="node-config-input-f_blank_stale_status"> Auto-clear</label>
<div style="display: inline-block; position: relative; width: 200px; height: 20px;">
<div style="position: absolute; left: 0px; right: 40px;">
<input type="checkbox" id="node-config-input-f_blank_stale_status" style="display:inline-block; width:15px; vertical-align:baseline;" autocomplete="off">
<span>Enable</span>
</div>
</div>
</div>
</div>
</div>
<div class="musiccast-form-section-description"><p>Set the maximum amount of time to wait for operations. A device is a Musiccast device on the local network. A service is a streaming provider like Tidal or Spotify. Times are in milliseconds.</p>
<div class="musiccast-form-section-content">
<div class="form-row musiccast-form-row">
<label for="node-config-input-command_timeout"> Device</label>
<div style="display: inline-block; position: relative; width: 200px; height: 20px;">
<div style="position: absolute; left: 0px; right: 40px;">
<input type="text" id="node-config-input-command_timeout" placeholder="2000">
</div>
</div>
</div>
<div class="form-row musiccast-form-row">
<label for="node-config-input-services_timeout"> Service</label>
<div style="display: inline-block; position: relative; width: 200px; height: 20px;">
<div style="position: absolute; left: 0px; right: 40px;">
<input type="text" id="node-config-input-services_timeout" placeholder="2000">
</div>
</div>
</div>
</div>
</div>
<div class="musiccast-form-section-description"><p>Choose the language to use when searching media.</p>
<div class="musiccast-form-section-content">
<div class="form-row musiccast-form-row ">
<label for="node-config-input-list_language"> Language</label>
<div style="display: inline-block; position: relative; width: 70%; height: 20px;">
<div style="position: absolute; left: 0px; right: 40px;">
<input type="text" style="width:100px" id="node-config-input-list_language">
</div>
</div>
</div>
</div>
</div>
<div id="node-input-section-advanced" class="musiccast-form-section-collapsible">
<label for="node-input-section-advanced-title"><i class="fa fa-plus-square"></i> Advanced</label>
<span id="node-input-section-advanced-title"></span>
</div>
<div class="musiccast-form-section-collapsible-content">
<div class="form-row musiccast-form-row ">
<label for="node-config-input-f_use_debug"> Debug</label>
<div style="display: inline-block; position: relative; width: 70%; height: 20px;">
<div style="position: absolute; left: 0px; right: 40px;">
<input type="checkbox" id="node-config-input-f_use_debug"
style="display:inline-block; width:15px; vertical-align:baseline;" autocomplete="off">
<span>Send detailed information to the console.</span>
</div>
</div>
</div>
</div>
<div class="form-row musiccast-form-row-ah">
<input type="text" id="node-input-inputs_limited">
</div>
<div class="form-row musiccast-form-row-ah">
<input type="text" id="node-input-sound_programs_limited">
</div>
</script>
<script type="text/html" data-help-name="musiccast-config">
<link rel="stylesheet" href="resources/node-red-contrib-musiccast/style.css"/>
<p>This node holds the configuration for a single Musiccast system. </p>
<h4>Basic Configuration</h4>
<p>
The following item is required.
</p>
<dl>
<dt>Name</dt>
<dd>Enter a name for your Musiccast system</dd>
</dl>
<h3>Device Editor</h3>
<p>
At least one device must be entered in the device editor to establish a Musiccast network. Once an address is
entered, Node-RED
will attempt to find the device on the network and autofill the name and model. </p>
<p>
When a device is first added, a call will be made to retrieve some information that is stored and used to
simplify configuration of the individual nodes. This information can be updated at any time using the refresh
button at the bottom of the editor.</p>
<p>
<dd>There are two buttons at the bottom of the editor to be aware of:<p>
<ul>
<li>[ <i class="fa fa-plus"> add device ]</i> Click to add a new device.</li>
<li>[ <i class="fa fa-refresh">refresh all ]</i> Click to refresh the stored configuration of all devices in the
list
</li>
</ul>
</p></dd>
<dl>
<dt>Status</dt>
<dd>Shows the status of the device once configured. The meaning of the icons is as follows:<p>
<ul>
<li><i class="fa fa-question-circle"></i> The device IP address has not been entered yet.</li>
<li><i class="fa fa-spinner"></i> Node-RED is actively looking for the device.</li>
<li><i class="fa fa-check-square"></i> Node-RED has found the device and confirmed it to be compatible.
</li>
<li><i class="fa fa-exclamation-triangle"></i> An error occurred while looking for the device. This may
only
mean it's offline or powered off.
</li>
</ul>
</p></dd>
<dt>Address</dt>
<dd><p>Enter an IP address for the device.</p>
<p><i>NOTE:</i> Whenever an IP address is entered or changed, Node-RED will attempt to connect to the device
and determine its disposition.</p>
</dd>
<dt>Name</dt>
<dd><p>Enter the device name as you would like it to appear throughout Node-RED. This setting is for use within
Node-RED only and does not change the setting on the device itself. </p>
</dd>
<dt>Model</dt>
<dd> This is the device model as discovered. It can be left as is but can be changed to a different value if
desired. Note that this setting is for informative purposes only and does not affect device communication.
</dd>
</dl>
<h3>Zone Editor</h3>
<p>
Labels for zones can be given custom names. Any zone can be referenced throughout Node-RED by the original system name (in lower case) or the custom name.
</p>
<h3>Advanced Configuration</h3>
<dl>
<dt>Palette timeout</dt>
<dd> Setting this to true will cause all palette status messages to automatically clear after 30 seconds.</dd>
<dt>Device timeout</dt>
<dd> Number of milliseconds to wait for devices to respond before they're considered unreachable.</dd>
<dt>Services timeout</dt>
<dd> Number of milliseconds to wait for network services before they're considered unreachable (Tidal, Pandora,
etc.).
</dd>
<dt>Language</dt>
<dd> Two-letter code for the language to be used when working with USB and network-based media lists.</dd>
<dt>Debug</dt>
<dd> Tick the box to allow for advanced information to be sent to the console and/or debug window.</dd>
</dl>
<h3>Additional Information</h3>
<h4>Output messages</h4>
<p>
If a valid response is received from a device, a command is considered successful and the resposne will be output as the message payload. It is possible that a valid response contains an error from Musiccast and should be dealt with if so. In this situation, the response payload will include the following two attributes to act on:
<ul>
<li>response_code - An integer</li>
<li>Response code message - A string value</li>
</ul>
</p>
<h4>Exceptions</h4>
<p>
Exceptions will raise an error when the confirmation of a node or input to a node can not be understood by Node-RED. The resulting error condition can be handled using a [catch] node or by processing the error message that is sent as output. The text of certain errors will be shown on the palette and will remain there until the issue is cleared up.
</p>
<h4>Shared variables</h4>
<p>Several system variables can be used to track status or supply dynamic configurations to Musiccast nodes. These
system variables have reserved names and must not be used for any other purpose within Node-RED.</p>
<dl>
<code>flow.musiccast_zone</code>
<div style="padding-left: 15px"> Used to configure the zone. This variable is viewable and settable by nodes on
the same flow.
</div>
<code>global.musiccast_zone</code>
<div style="padding-left: 15px"> Global variable used to configure the zone. The value is viewable and settable
by all nodes on all flows.
</div>
<code>flow.musiccast_device</code>
<div style="padding-left: 15px"> Used to configure a node's device. This variable is viewable and settable by
nodes on the same flow.
</div>
<code>global.musiccast_device</code>
<div style="padding-left: 15px"> Global variable used to configure a node's device. The value is viewable and
settable by all nodes on all flows.
</div>
<code>flow.musiccast_input</code>
<div style="padding-left: 15px"> Flow variable used to configure a node's input. This variable is viewable and
settable by nodes on the same flow.
</div>
<code>global.musiccast_input</code>
<div style="padding-left: 15px"> Global variable used to configure a node's input. The value is viewable and
settable by all nodes on all flows.
</div>
</dl>
<h4>Response code reference</h4>
<!-- BEGIN RESPONSES HELP -->
<dl>
<dt>0</dt>
<dd>Successful request</dd><p></p>
<dt>1</dt>
<dd>Initializing</dd><p></p>
<dt>2</dt>
<dd> Internal Error</dd><p></p>
<dt>3</dt>
<dd>Invalid Request</dd><p></p>
<dt>4</dt>
<dd>Invalid Parameter</dd><p></p>
<dt>5</dt>
<dd>Guarded</dd><p></p>
<dt>6</dt>
<dd>Time Out</dd><p></p>
<dt>99</dt>
<dd>Firmware Updating</dd><p></p>
<dt>100</dt>
<dd>Access Error</dd><p></p>
<dt>101</dt>
<dd>Other Errors</dd><p></p>
<dt>102</dt>
<dd>Wrong User Name</dd><p></p>
<dt>103</dt>
<dd>Wrong Password</dd><p></p>
<dt>104</dt>
<dd>Account Expired</dd><p></p>
<dt>105</dt>
<dd>Account Disconnected/Gone Off/Shut Down</dd><p></p>
<dt>106</dt>
<dd>Account Number Reached to the Limit</dd><p></p>
<dt>107</dt>
<dd>Server Maintenance</dd><p></p>
<dt>108</dt>
<dd>Invalid Account</dd><p></p>
<dt>109</dt>
<dd>License Error</dd><p></p>
<dt>110</dt>
<dd>Read Only Mode</dd><p></p>
<dt>111</dt>
<dd>Max Stations</dd><p></p>
<dt>112</dt>
<dd>Access Denied</dd><p></p>
<dt>113</dt>
<dd>There is a need to specify the additional destination Playlist</dd><p></p>
<dt>114</dt>
<dd>There is a need to create a new Playlist</dd><p></p>
<dt>115</dt>
<dd>Simultaneous logins has reached the upper limit</dd><p></p>
<dt>200</dt>
<dd>Linking in progress</dd><p></p>
<dt>201</dt>
<dd>Unlinking in progress</dd><p></p>
<dt>5000</dt>
<dd>Unknown error.</dd><p></p>
</dl>
<!-- END RESPONSES HELP -->
<h4>Yamaha Documentation</h4>
<ul>
<li><a href="resources/node-red-contrib-musiccast/Yamaha YXC Advanced API.pdf" target="_blank">Yamaha YXC
Advanced API</a></li>
<li><a href="resources/node-red-contrib-musiccast/YXC Yamaha API Basic.pdf" target="_blank">YXC Yamaha API
Basic</a></li>
</ul>
<h4>Collection Information</h4>
<ul>
<li>Version: 4.5.2</li>
</ul>
<div style="height: 700px;"></div>
<span>This space intentionally left blank</span>
</script>