victron-vrm-api
Version:
Interface with the Victron Energy VRM API
499 lines (445 loc) • 25.5 kB
HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script type="text/javascript">
function checkSelectedAPIType() {
[
'users', 'installations', 'widgets', 'dynamic-ess', 'instalations_and_widgets'
].map( x => { $('#input-'+x).hide() })
const selected = $('select#node-input-api_type').val()
$('#input-'+selected).show()
if (selected === 'installations' || selected === 'widgets') {
$('#input-instalations_and_widgets').show()
}
console.log('selected api type', selected)
const isDynamicESS = selected === 'installations' &&
$('#node-input-installations').val() === 'stats' &&
$('#node-input-attribute').val() === 'dynamic_ess';
if (isDynamicESS) {
$('#input-transform-price-schedule').show();
} else {
$('#input-transform-price-schedule').hide();
$('#node-input-transform_price_schedule').prop('checked', false);
}
}
function checkSelectedInstallations() {
[
'stats', 'gps-download', 'fetch--ess-schedules', 'post-adjust-consumption'
].map( x => { $('#input-'+x).hide() })
const selected = $('select#node-input-installations').val()
if (selected === 'stats') {
$('#input-stats').show()
}
if (selected === 'gps-download' ) {
$('#input-gps-download').show()
}
if (selected === 'fetch-dynamic-ess-schedules' ) {
$('#input-fetch-dynamic-ess-schedules').show()
}
if (selected === 'post-adjust-consumption') {
$('#input-post-adjust-consumption').show()
}
const apiType = $('#node-input-api_type').val();
const isDynamicESS = apiType === 'installations' &&
selected === 'stats' &&
$('#node-input-attribute').val() === 'dynamic_ess';
if (isDynamicESS) {
$('#input-transform-price-schedule').show();
} else {
$('#input-transform-price-schedule').hide();
$('#node-input-transform_price_schedule').prop('checked', false);
}
}
function checkSelectedUsers() {
[
'users-idUser'
].map( x => { $('#input-'+x).hide() })
const selected = $('select#node-input-usersQuery').val()
if (selected !== 'me') { $('#input-users-idUser').show() }
}
RED.nodes.registerType('vrm-api', {
category: 'Victron Energy',
paletteLabel: 'VRM API',
color: '#f7ab3e',
defaults: {
vrm: {value: "", type: "config-vrm-api", required: false},
name: { value: "" },
api_type: { value: "", validate: RED.validators.regex(/^(users|installations|widgets|dynamic-ess)$/)},
// users
idUser: { value: "", validate: RED.validators.regex(/^(|[0-9]{1,12})$/)},
usersQuery: { value: "me", required: false},
// installations
idSite: { value: "", validate: RED.validators.regex(/^(|[0-9]{1,12}|\{\{(node|flow|global)\..*\}\}|)$/)},
installations: { value: "", required: false},
attribute: {value: "", required: false},
stats_interval: {value: ""},
show_instance: {value: false},
stats_start: {value: ""},
stats_end: {value: ""},
use_utc: { value: false },
gps_start: {value: '', required: false },
gps_end: {value: '', required: false },
// widgets
// taking idSite from installations
widgets: { value: "", required: false},
instance: {value: "", required: false},
store_in_global_context: {value: false},
verbose: { value: false },
transform_price_schedule: { value: false },
outputs: {value: 1}
},
inputs: 1,
outputs: 1,
icon: "victronenergy.svg",
label: function () {
if (this.name) {
return this.name
}
var label = this.api_type
switch (this.api_type) {
case 'users': {
label += ' - ' + (this.usersQuery || 'me')
}
break;
case 'installations': {
if ( this.installations === 'stats' ) {
label += ' - Stats: ' + this.attribute
} else {
label += ' - ' + this.installations
}
}
break;
case 'widgets': {
label += ' - ' + this.widgets
}
}
return label;
},
outputLabels: function(index) {
const isDynamicESS = this.installations === 'fetch-dynamic-ess-schedules';
if (isDynamicESS && this.transform_price_schedule) {
return index === 0 ? "raw data" : "price schedule";
}
return "output";
},
oneditprepare: function oneditprepare() {
// Backwards compatibility: migrate old 'users' field to 'usersQuery'
if (this.users && !this.usersQuery) {
this.usersQuery = this.users;
}
checkSelectedAPIType()
checkSelectedUsers()
// NEW: Function to update outputs count
const updateOutputs = () => {
const isDynamicESS = $('#node-input-installations').val() === 'stats' &&
$('#node-input-attribute').val() === 'dynamic_ess';
const transformEnabled = $('#node-input-transform_price_schedule').is(':checked');
const outputCount = (isDynamicESS && transformEnabled) ? 2 : 1;
// Update the hidden field
$('#node-input-outputs').val(outputCount);
// Update the node's outputs property
this.outputs = outputCount;
};
// Show/hide transform checkbox based on installation selection
$('#node-input-installations').on('change', function() {
const isDynamicESS = $(this).val() === 'installations' &&
$('#node-input-attribute').val() === 'dynamic_ess';
$('#input-transform-price-schedule').toggle(isDynamicESS);
updateOutputs();
});
// Update outputs when checkbox changes
$('#node-input-transform_price_schedule').on('change', updateOutputs);
// Initial state
const isDynamicESS = $('#node-input-installations').val() === 'stats' &&
$('#node-input-attribute').val() === 'dynamic_ess';
$('#input-transform-price-schedule').toggle(isDynamicESS);
// Set initial output count
updateOutputs();
flatpickr("#node-input-gps_start", {
enableTime: true,
dateFormat: "Y-m-d H:i"
})
flatpickr("#node-input-gps_end", {
enableTime: true,
dateFormat: "Y-m-d H:i"
})
}
});
</script>
<script type="text/html" data-template-name="vrm-api">
<input type="hidden" id="node-input-outputs">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-vrm"><i class="fa fa-tag"></i> VRM</label>
<input type="text" id="node-input-vrm" placeholder="VRM" style="width: 20%;">
</div>
<div class="form-row">
<label for="node-input-api_type"><i class="fa fa-location-arrow"></i> API type</label>
<select id="node-input-api_type" required onchange="checkSelectedAPIType()">
<option value="users">Users</option>
<option value="installations">Installations</option>
<option value="widgets">Widgets</option>
</select>
</div>
<div id="input-users">
<div class="form-row">
<label for="node-input-usersQuery"><i class="fa fa-location-arrow"></i> Users</label>
<select id="node-input-usersQuery" onchange="checkSelectedUsers()">
<option value="me" selected="selected">Basic user information</option>
<option value="installations">All installations/sites</option>
</select>
</div>
<div id="input-users-idUser">
<div class="form-row">
<label for="node-input-idUser"><i class="fa fa-id-card"></i> User id</label>
<input type="text" id="node-input-idUser" placeholder="User id">
</div>
</div>
</div>
<div id="input-instalations_and_widgets">
<div class="form-row">
<label for="node-input-idSite"><i class="fa fa-id-card"></i> VRM site id</label>
<input type="text" id="node-input-idSite" placeholder="VRM site id">
</div>
</div>
<div id="input-installations">
<div class="form-row">
<label for="node-input-installations"><i class="fa fa-location-arrow"></i> Installation</label>
<select id="node-input-installations" onchange="checkSelectedInstallations()">
<option value="alarms">Get Alarms</option>
<option value="post-alarms">Add Alarm</option>
<option value="gps-download">GPS tracks</option>
<option value="system-overview">Connected devices for a given installation</option>
<option value="diagnostics">Diagnostic data for an installation</option>
<option value="tags">Get installation tags</option>
<option value="stats">Installation stats</option>
<option value="dynamic-ess-settings">Get Dynamic ESS configuration</option>
<option value="patch-dynamic-ess-settings">Modify Dynamic ESS configuration</option>
<option value="fetch-dynamic-ess-schedules">Fetch Dynamic ESS schedules</option>
<option value="post-adjust-consumption">Adjust Consumption Forecast</option>
</select>
</div>
<div id="input-post-adjust-consumption" style="display:none;">
<div class="form-row">
<p style="margin-left: 102px; color: #666; max-width: 380px;">
Send <code>msg.payload</code> as a single object or array of hourly entries:<br>
<code>{ "offset": 2, "ev": 10500, "hp": 4000, "base": 2000 }</code><br>
<code>"offset"</code> is hours from the current hour (0 = now).
When all three of <code>ev</code>, <code>hp</code>, and <code>base</code> are provided,
the total is computed automatically. Omit any key to leave it unchanged.
Use <code>-1</code> for <code>"ev"</code> or <code>"hp"</code> to reset that hour to the base forecast.
</p>
<p style="margin-left: 102px; color: #666; max-width: 380px;">
To reset hours to the base forecast, set <code>msg.reset</code> to an array of offsets
instead of using <code>msg.payload</code>:<br>
<code>msg.reset = [0, 1, 2]</code> (offsets from current hour)
</p>
<p style="margin-left: 102px; color: #888; font-style: italic; font-size: 0.9em; max-width: 380px;">
<code>offset</code> can also be an absolute Unix timestamp (full hours only, future only).
Requires Full control or Technician access on the installation.
</p>
</div>
</div>
<div id="input-fetch-dynamic-ess-schedules" style="display:none;">
<div class="form-row">
<p style="margin-left: 102px; color: #666; font-style: italic; max-width: 380px;">
The API always returns buckets of 15 minute intervals. If you have hourly prices configured for your site, the buckets will contain the same price for 4 records in a row.
The records returned will contain data from the beginning of today until the end of tomorrow (if the prices are in, else until the end of today).
</p>
</div>
</div>
<div id="input-stats">
<div class="form-row">
<label for="node-input-attribute"><i class="fa fa-location-pencil" ></i> Attribute</label>
<select id="node-input-attribute" onchange="checkSelectedInstallations()">
<option value="Bc">Battery direct use</option>
<option value="Bg">Battery to grid</option>
<option value="bs">Battery SoC</option>
<option value="Gc">Grid direct use</option>
<option value="Gb">Grid to battery</option>
<option value="Pc">Solar direct use</option>
<option value="Pb">Solar to battery</option>
<option value="kwh">Total kWh</option>
<option value="consumption">Consumption</option>
<option value="evcs">EV consumption</option>
<option value="evE">EV Charging Consumption</option>
<option value="dhE">Heating Consumption</option>
<option value="daE">AC Load Consumption</option>
<option value="vrm_consum_evcs">EV Charging Consumption</option>
<option value="vrm_consum_hp">Heating Consumption</option>
<option value="vrm_consum_ac">AC Load Consumption</option>
<option value="vrm_consum_base">Base Load Consumption</option>
<option value="vrm_consumption_fc">Consumption Forecast</option>
<option value="vrm_consum_evcs_fc">EV Charging Consumption Forecast</option>
<option value="vrm_consum_hp_fc">Heating Consumption Forecast</option>
<option value="vrm_pv_inverter_yield_fc">PV Inverter Yield Forecast</option>
<option value="vrm_pv_charger_yield_fc">PV Solar Charger Yield Forecast</option>
<option value="vrm_solar_irradiance">Solar Irradiance</option>
<option value="vrm_solar_irradiance_fc">Solar Irradiance Forecast</option>
<option value="solar_yield">Solar Yield</option>
<option value="total_solar_yield">Total Solar Yield</option>
<option value="solar_yield_forecast">Solar Yield Forecast</option>
<option value="dynamic_ess">Dynamic ESS</option>
</select>
</div>
<div class="form-row">
<label for="node-input-stats_interval"><i class="fa fa-barcode"></i> Interval</label>
<select id="node-input-stats_interval">
<option value="15mins">15 minutes</option>
<option selected="selected" value="hours">hours</option>
<option value="2hours">2 hours</option>
<option value="days">days</option>
<option value="weeks">weeks</option>
<option value="months">months</option>
<option value="years">years</option>
</select>
</div>
<div class="form-row">
<label for="node-input-stats_start"><i class="fa fa-calendar"></i> Start</label>
<select id="node-input-stats_start">
<option value="-31536000">-1 year</option>
<option value="-7257600">-12 weeks</option>
<option value="-2419200">-4 weeks</option>
<option value="-604800">-1 week</option>
<option value="-259200">-72 hours</option>
<option value="-172800">-48 hours</option>
<option value="-86400">-24 hours</option>
<option value="boy">beginning of yesterday</option>
<option value="bod">beginning of day</option>
<option value="bot">beginning of tomorrow</option>
<option selected="selected" value="0">now</option>
</select>
</div>
<div class="form-row">
<label for="node-input-stats_end"><i class="fa fa-calendar"></i> End</label>
<select id="node-input-stats_end">
<option value="0">now</option>
<option value="eod">end of day</option>
<option value="eoy">end of yesterday</option>
<option value="eot">end of tomorrow</option>
<option value="eoyr">end of year</option>
<option selected="selected" value="86400">+24 hours</option>
### tfx add more interval options
<option selected="selected" value="172800">+48 hours</option>
<option selected="selected" value="259200">+72 hours</option>
<option selected="selected" value="604800">+1 week</option>
### /tfx
</select>
</div>
<div class="form-row" style="margin-bottom:0px;">
<input type="checkbox" id="node-input-use_utc" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
<label style="min-width:390px" for="node-input-use_utc"><i class="fa fa-power"></i> Use universal time (UTC) instead of the local time?</label>
</div>
<div class="form-row" style="margin-bottom:0px;">
<input type="checkbox" id="node-input-show_instance" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
<label style="min-width:390px" for="node-input-show_instance"><i class="fa fa-power"></i> Group attributes by instance?</label>
</div>
</div>
</div>
<div id="input-gps-download">
<div class="form-row">
<label for="node-input-gps_start"><i class="fa fa-calendar"></i> Timestamp from which to fetch data</label>
<input type="text" id="node-input-gps_start" placeholder="Unix timestamp">
</div>
<div class="form-row">
<label for="node-input-gps_end"><i class="fa fa-calendar"></i> Timestamp tp which to fetch data</label>
<input type="text" id="node-input-gps_end" placeholder="Unix timestamp">
</div>
</div>
<div id="input-widgets">
<div class="form-row">
<label for="node-input-widgets"><i class="fa fa-location-arrow"></i> Widgets</label>
<select id="node-input-widgets">
<option value="BatterySummary">Battery summary data</option>
<option value="BMSDiagnostics">BMS diagnostics summary data</option>
<option value="HistoricData">Historic summary data</option>
<option value="IOExtenderInOut">IO extender input and output summary data</option>
<option value="LithiumBMS">Lithium BMS summary data</option>
<option value="DCMeter">DC meter summary data</option>
<option value="EvChargerSummary">EV charger summary data</option>
<option value="MeteorologicalSensor">Meteorological summary data</option>
<option value="GlobalLinkSummary">GlobalLink summary data</option>
<option value="MotorSummary">Motor summary data</option>
<option value="PVInverterStatus">PV inverter summary data</option>
<option value="SolarChargerSummary">Solar charger summary data</option>
<option value="Status">System overview summary data</option>
<option value="TankSummary">Tank summary data</option>
<option value="TempSummaryAndGraph">Temperature summary data</option>
</select>
</div>
<div class="form-row">
<label for="node-input-instance"><i class="fa fa-id-card"></i> Instance</label>
<input type="text" id="node-input-instance" placeholder="Instance for which to retrieve data, defaults to 0">
</div>
</div>
<div class="form-row" style="margin-bottom:0px;">
<input type="checkbox" id="node-input-store_in_global_context" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
<label style="min-width:390px" for="node-input-store_in_global_context"> Store the response in the global context?</label>
</div>
<div class="form-row" style="margin-bottom:0px;">
<input type="checkbox" id="node-input-verbose" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
<label style="min-width:390px" for="node-input-verbose"> Verbose: show the used <em>url</em> in the debug tab?</label>
</div>
<div class="form-row" id="input-transform-price-schedule" style="margin-bottom:0px; display:none;">
<input type="checkbox" id="node-input-transform_price_schedule" style="display:inline-block; margin-left:8px; width:auto; vertical-align:top;">
<label style="min-width:390px" for="node-input-transform_price_schedule"> Transform price schedule to second output?</label>
</div>
</script>
<script type="text/markdown" data-help-name="vrm-api">
Interface with the Victron Energy VRM API.
### Inputs
: payload (string|number|json) : the trigger to query the VRM API
### Outputs
: payload (json) : the vrm answer
The output of the answer depends on the selected configuration. See the [VRM API documentation](https://vrm-api-docs.victronenergy.com/#)
in case you need assistance with interpreting the output.
Note that the site id is the multi-digit number you see in the url of your vrm-site. If you need the user id, query
the _basic user information_ first.
### Configuration
: Name (string) : The name of the node
: VRM (config) : The configuration node
: VRM site id (number) : The site to query
: API type (string) : The query type
: Store (boolean) : Store the respones in the global context?
: Verbose (boolean) : Show the used _url_ in the debug tab?
There are currently 3 API types to choose from:
- Users
- Installations
- Widgets
Depending on the API type, more or less extra fields appear.
In case of installation `stats` there appear some extra configuration options
: Attribute (string) : Which attribute to fetch
: Interval (string) : Time between retrieved data points
: Show instance (boolean) : If checked, attributes will be grouped by instance
: Start (integer) : Timestamp from which to fetch data
: End (integer) : Timestamp to which to fetch data
: UTC (boolean) : Use universal time (UTC) instead of the local time?
: Group by instance (boolean): Group attributes by instance?
In case of installation `dess` there are even more configuration options.
Note that instead of filling out the number of the VRM site id for installations and widgets in the box, you
can also use context variables, e.g. `{{flow.siteId}}` or `{{global.vrmId}}`. This allows to query the site
that has been set in this context field. Of course you need to make sure that context contains a valid VRM site id first.
There is a comparable trick if you want the VRM API credentials to be picked up via another node. If you put the credentials
into the flow context `vrm_api.credentials.token` and set the VRM configuration node to "none", the VRM API node will use the
credentials from the flow context instead.
### Details
This node makes it easy to use the VRM API for data retrieval. Though not all possible API calls have been implemented, it can
be used for retrieving and creating alarms and fetching installations statistics. It can also be used to retrieve the solar forecast
data.
In order to allow the node to query your site, an [access-token](https://vrm.victronenergy.com/access-tokens) needs to be created and filled
out in the configuration node.
If the data is to be stored in the global context, it will be saved under `vrm_api.${site_id}_${installation}`.
### Advanced usage
If you have really spefic needs to query the VRM API, which aren't part of the node (yet), you can input a
payload that contains `msg.query`, `msg.method` and `msg.url`. The node will then use these values to query
the VRM API. You can also overrule the `msg.topic` for these queries. Note that you _must_ set the `msg.method`
when using this feature.
The `msg.url` part is _everything_ after the `https://vrmapi.victronenergy.com/v2/` part.
### References
Please use either the issues on the GitHub site or the Node-RED space on our community for questions, troubleshooting and suggestions.
- [GitHub](https://github.com/dirkjanfaber/victron-vrm-api) - The nodes GitHub repository.
- [Community](https://community.victronenergy.com/tag/node-red) - Node-RED space in the Victron Energy community.
- [VRM API documentation](https://vrm-api-docs.victronenergy.com/#) - The VRM API documentation.
</script>