Code samples
Learn how to communicate with the Switch OpenADR 3 VTN API through simple code samples.
Last updated
Was this helpful?
Learn how to communicate with the Switch OpenADR 3 VTN API through simple code samples.
Last updated
Was this helpful?
Using the Switch OpenADR 3 VTN API is as simple as communicating with any REST API. Below you will find examples of how to authenticate with the VTN prior to making any request to it and it follows with example of how to make a simple request.
Information about the VTN URL you can find on the page, details about the authentication parameters at , and information about the available API endpoints at page.
Samples shown below are not meant to be production-ready code or to be used as-is. They are meant to illustrate the use of an API or feature in the simplest way possible. For this reason, these samples make certain assumptions on hosting, authentication, request/response handling etc.
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace ConsoleAppOadr3SamplesDocs
{
internal class Application
{
private const string TokenProviderUrl = "<token_provider_url>";
private const string ClientId = "<your_client_id>";
private const string ClientSecret = "<your_client_secret>";
private const string GrantType = "client_credentials";
private const string Scope = "<scope>";
private const string VtnUrl = "<vtn_url>";
static async Task Main()
{
// NOTE: This is just a very simple example for authenticating.
// Ideally you would use an established library instead, allowing token re-use
// and refresh token when needed.
var accessToken = await GetAccessToken();
// NOTE: Another way to get access token is to call the Auth endpoint of the VTN but
// this way is to be used for testing purposes only.
// Search for available programs.
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetAsync($"{VtnUrl}/programs");
var content = await response.Content.ReadAsStringAsync();
var programs = JsonSerializer.Deserialize<Program[]>(content);
var program = programs.FirstOrDefault();
// Search for available events with filters.
response = await client.GetAsync($"{VtnUrl}/events?programID={program.Id}&skip=1&limit=5");
content = await response.Content.ReadAsStringAsync();
var events = JsonSerializer.Deserialize<Event[]>(content);
// Create subscription.
var subscription = new Subscription
{
ProgramId = program.Id,
ClientName = "MyVEN",
ObjectOperations = new List<SubscriptionObjectOperation>
{
new()
{
Objects = new List<string> { "EVENT", "REPORT" },
Operations = new List<string> { "POST", "PUT", "DELETE" },
CallbackUrl = "https://mycompany.com/callback"
}
}
};
var payload = new StringContent(JsonSerializer.Serialize(subscription), Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
await client.PostAsync($"{VtnUrl}/subscriptions", payload);
}
private static async Task<string> GetAccessToken()
{
using var client = new HttpClient();
var response = await client.PostAsync(TokenProviderUrl, new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "grant_type", GrantType },
{ "client_id", ClientId },
{ "client_secret", ClientSecret },
{ "scope", Scope }
}));
var content = await response.Content.ReadAsStringAsync();
var token = JsonSerializer.Deserialize<Token>(content);
return token.AccessToken;
}
private class Token
{
[JsonPropertyName("access_token")]
public string AccessToken { get; init; }
}
private class Program
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("programName")]
public string ProgramName { get; set; }
// Remaining fields omitted for this example, see the API reference documentation for the full list.
}
private class Event
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("programID")]
public string ProgramId { get; set; }
[JsonPropertyName("eventName")]
public string EventName { get; set; }
// Remaining fields omitted for this example, see the API reference documentation for the full list.
}
private class Subscription
{
[JsonPropertyName("clientName")]
public string ClientName { get; set; }
[JsonPropertyName("programID")]
public string ProgramId { get; set; }
[JsonPropertyName("objectOperations")]
public List<SubscriptionObjectOperation> ObjectOperations { get; set; }
// Remaining fields omitted for this example, see the API reference documentation for the full list.
}
private class SubscriptionObjectOperation
{
[JsonPropertyName("objects")]
public List<string> Objects { get; set; }
[JsonPropertyName("operations")]
public List<string> Operations { get; set; }
[JsonPropertyName("callbackUrl")]
public string CallbackUrl { get; set; }
// Remaining fields omitted for this example, see the API reference documentation for the full list.
}
}
}
const VTN_URL = 'https://qa-vtn3.switchmarket.se/api/openadr/v1';
const POLL_MS = 10_000;
// See https://developer.switchmarket.se/openadr-3/authentication/vtn-credentials
// and https://developer.switchmarket.se/openadr-3/authentication/token-endpoint
const TOKEN_URL = '';
const TOKEN_SCOPE = '';
const CLIENT_ID = '';
const CLIENT_SECRET = '';
async function run() {
// A proper integration should utilize an oauth client library to be able to utilize refresh tokens
// and more, e.g https://github.com/panva/node-openid-client
const tokenResponse = await fetch(TOKEN_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')
},
body: `grant_type=client_credentials&scope=${TOKEN_SCOPE}`
});
if (!tokenResponse.ok) {
console.error(await tokenResponse.json());
process.exit(1);
}
const token = (await tokenResponse.json()).access_token;
async function readEvents() {
const params = new URLSearchParams({ 'x-start': new Date().toISOString() }).toString();
const eventResponse = await fetch(`${VTN_URL}/events?${params}`, {
headers: {
authorization: `Bearer ${token}`
}
});
if (!eventResponse.ok) {
console.error(await eventResponse.json());
process.exit(1);
}
const events = await eventResponse.json();
if (events.length > 0) {
console.log();
console.log(`Found ${events.length} event(s)...`)
} else {
console.log(`Found no events... Trying again in ${POLL_MS} ms`);
}
for (const event of events) {
const resourceId = event.targets.find(t => t.type === 'RESOURCE_NAME').values[0];
const payloadLimitVersion = event.intervals[0].payloads.find(p => p.type === 'POWER_LIMIT_VERSION').values[0];
console.log();
console.log(`ID = ${event.id}`);
console.log(`RESOURCE ID = ${resourceId}`)
console.log(`START = ${event.intervalPeriod.start}`);
console.log(`DURATION = ${event.intervalPeriod.duration}`);
// For a production asset the payload type will instead be 'PRODUCTION_POWER_LIMIT'
console.log(`LIMIT = ${event.intervals[0].payloads.find(p => p.type === 'CONSUMPTION_POWER_LIMIT').values[0]} KW`);
console.log(`LIMIT VERSION = ${payloadLimitVersion}`);
// See https://developer.switchmarket.se/openadr-3/payloads#openadr-acknowledgement-report
if (event.reportDescriptors.some(r => r.payloadType === 'POWER_LIMIT_ACKNOWLEDGEMENT')) {
const params = new URLSearchParams({ 'eventID': event.id }).toString();
const reportResponse = await fetch(`${VTN_URL}/reports?${params}`, {
headers: {
authorization: `Bearer ${token}`
}
});
if (!reportResponse.ok) {
console.error(await reportResponse.json());
process.exit(1);
}
const reports = await reportResponse.json();
// Check for existing acknowledgement - each event should only be acknowledged once for each power limit version
const acks = reports.filter(r => r.payloadDescriptors.some(pd => pd.payloadType === 'POWER_LIMIT_ACKNOWLEDGEMENT'));
const ackedVersions = acks.map(r => r.resources[0].intervals[0].payloads.find(p => p.type === 'POWER_LIMIT_ACKNOWLEDGEMENT').values[0]);
if (!ackedVersions.includes(payloadLimitVersion)) {
console.log();
if (acks.length > 0) {
console.log(`Event has been acknowledged for power limit version(s) ${ackedVersions.join(', ')}, but the version is now ${payloadLimitVersion}`);
}
console.log('Sending acknowledgement...');
const ackResponse = await fetch(`${VTN_URL}/reports`, {
method: 'POST',
headers: {
authorization: `Bearer ${token}`,
'content-type': 'application/json'
},
body: JSON.stringify({
eventID: event.id,
programID: event.programID,
clientName: "<client name>",
payloadDescriptors: [{
payloadType: "POWER_LIMIT_ACKNOWLEDGEMENT"
}],
resources: [{
resourceName: resourceId,
intervalPeriod: event.intervalPeriod,
intervals: [{
id: 0,
payloads: [{
type: "POWER_LIMIT_ACKNOWLEDGEMENT",
values: [payloadLimitVersion]
}]
}]
}]
})
});
if (!ackResponse.ok) {
console.error(await ackResponse.json());
process.exit(1);
} else {
console.log('Acknowledgement sent sucessfully');
}
} else {
console.log();
console.log('Event already acknowledged');
}
}
}
}
await readEvents();
setInterval(readEvents, POLL_MS);
}
run();
The Switch platform has an official client library for .NET which you can find .