Skip to content

Instantly share code, notes, and snippets.

@yholkamp
Forked from erdebee/frank-onbalansmarkt.js
Last active August 3, 2025 13:25
Show Gist options
  • Save yholkamp/8e6af834be1f277536e76dd355d523dd to your computer and use it in GitHub Desktop.
Save yholkamp/8e6af834be1f277536e76dd355d523dd to your computer and use it in GitHub Desktop.
Post daily frank energie trading results to onbalansmarkt
class FrankEnergie {
constructor(authToken = null, refreshToken = null) {
this.DATA_URL = "https://frank-graphql-prod.graphcdn.app/";
this.auth = authToken || refreshToken ? { authToken, refreshToken } : null;
}
async query(queryData) {
const headers = {
'Content-Type': 'application/json',
...(this.auth && { 'Authorization': `Bearer ${this.auth.authToken}` })
};
try {
const response = await fetch(this.DATA_URL, {
method: 'POST',
headers,
body: JSON.stringify(queryData)
});
const data = await response.json();
if (data.errors) {
for (const error of data.errors) {
if (error.message === "user-error:auth-not-authorised") {
throw new Error("Authentication required");
}
}
}
return data;
} catch (error) {
throw new Error(`Request failed: ${error.message}`);
}
}
async login(username, password) {
const query = {
query: `
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
authToken
refreshToken
}
}
`,
operationName: "Login",
variables: { email: username, password }
};
const response = await this.query(query);
this.auth = response.data.login;
return this.auth;
}
async getPrices(startDate, endDate) {
const query = {
query: `
query MarketPrices($startDate: Date!, $endDate: Date!) {
marketPricesElectricity(startDate: $startDate, endDate: $endDate) {
from
till
marketPrice
marketPriceTax
sourcingMarkupPrice
energyTaxPrice
}
marketPricesGas(startDate: $startDate, endDate: $endDate) {
from
till
marketPrice
marketPriceTax
sourcingMarkupPrice
energyTaxPrice
}
}
`,
variables: {
startDate: startDate.toISOString().split('T')[0],
endDate: endDate.toISOString().split('T')[0]
},
operationName: "MarketPrices"
};
return await this.query(query);
}
async getSmartBatteries() {
if (!this.auth) {
throw new Error("Authentication required");
}
const query = {
query: `
query SmartBatteries {
smartBatteries {
brand
capacity
createdAt
externalReference
id
maxChargePower
maxDischargePower
provider
updatedAt
}
}
`,
operationName: "SmartBatteries"
};
return await this.query(query);
}
async getSmartBatterySessions(deviceId, startDate, endDate) {
if (!this.auth) {
throw new Error("Authentication required");
}
const query = {
query: `
query SmartBatterySessions($startDate: String!, $endDate: String!, $deviceId: String!) {
smartBatterySessions(
startDate: $startDate
endDate: $endDate
deviceId: $deviceId
) {
deviceId
periodEndDate
periodStartDate
periodTradingResult
sessions {
cumulativeTradingResult
date
tradingResult
}
totalTradingResult
}
}
`,
operationName: "SmartBatterySessions",
variables: {
deviceId,
startDate: startDate.toISOString().split('T')[0],
endDate: endDate.toISOString().split('T')[0]
}
};
return await this.query(query);
}
isAuthenticated() {
return this.auth !== null;
}
}
class OnbalansMarkt {
constructor(apiKey) {
this.apiUrl = 'https://onbalansmarkt.com/nexus/api/live';
this.apiKey = apiKey;
}
async sendMeasurement({
timestamp,
batteryResult,
batteryResultTotal = null,
batteryCharge = null,
batteryPower = null,
deliveryToday = null,
productionToday = null,
loadBalancingActive = null,
solarResult = null,
chargerResult = null
}) {
// Validate required fields
if (!timestamp || !batteryResult) {
throw new Error('timestamp and batteryResult are required fields');
}
// Prepare the payload
const payload = {
timestamp,
batteryResult: batteryResult.toString(),
...(batteryResultTotal !== null && { batteryResultTotal: batteryResultTotal.toString() }),
...(batteryCharge !== null && { batteryCharge: batteryCharge.toString() }),
...(batteryPower !== null && { batteryPower: batteryPower.toString() }),
...(deliveryToday !== null && { deliveryToday: deliveryToday.toString() }),
...(productionToday !== null && { productionToday: productionToday.toString() }),
...(loadBalancingActive !== null && { loadBalancingActive: loadBalancingActive.toString() }),
...(solarResult !== null && { solarResult: solarResult.toString() }),
...(chargerResult !== null && { chargerResult: chargerResult.toString() })
};
try {
const response = await fetch(this.apiUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error sending measurement:', error);
throw error;
}
}
}
const frank = new FrankEnergie();
await frank.login("[email protected]", "mijnfrankpassword");
const onbalansmarkt = new OnbalansMarkt("API-KEY-ONBALANSMARKT.COM");
// Get all smart batteries
const batteries = await frank.getSmartBatteries();
console.log(batteries);
let yesterday = new Date();
yesterday.setHours(0,0,0,0);
yesterday.setDate(yesterday.getDate() - 1);
// Get sessions for a specific battery
if (batteries.data.smartBatteries.length > 0) {
const batteryId = batteries.data.smartBatteries[0].id;
const sessions = await frank.getSmartBatterySessions(
batteryId,
yesterday,
yesterday
);
console.log(sessions);
onbalansmarkt.sendMeasurement({
timestamp: yesterday.toISOString(),
batteryResult: sessions.data.smartBatterySessions.periodTradingResult,
loadBalancingActive: "off"
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment